forked from AkkomaGang/akkoma
Compare commits
58 commits
da7a8cd269
...
b865b3450f
Author | SHA1 | Date | |
---|---|---|---|
b865b3450f | |||
722e56b308 | |||
|
95e4018c1a | ||
772c209914 | |||
f32e288711 | |||
85137f591f | |||
f11a6eb8dd | |||
db7ad08d1e | |||
e4f2251e0f | |||
618cf7ff7f | |||
017b50550b | |||
92ba2802fb | |||
fd7f4874ba | |||
c40b45e675 | |||
9b6feb6657 | |||
3cf8c1eb31 | |||
152c43ac9e | |||
8d7b63a766 | |||
aa681d7e15 | |||
b0130bfa7b | |||
d72f9e39d9 | |||
429e2ac832 | |||
f8dffa6126 | |||
ffbf8304e0 | |||
59b886e86e | |||
22333f13e8 | |||
a8f8ecce31 | |||
e9f1897cfd | |||
aaf78e2b52 | |||
11ec9daa5b | |||
89ffc01c23 | |||
61641957cb | |||
37a1001b97 | |||
5796d81d98 | |||
7544939c83 | |||
5192e21e53 | |||
19ccdc8762 | |||
967c325b0d | |||
d3b9cfb03f | |||
ceeeefc707 | |||
366889f97c | |||
74dbea4cf8 | |||
|
8bca9a7dbe | ||
|
fcb5e4a48d | ||
|
b1e2f3f646 | ||
|
2f074a6966 | ||
|
fd35a66312 | ||
|
5022ecd766 | ||
d16eff1c0f | |||
55179d4214 | |||
e5a2548521 | |||
1245141779 | |||
5d23df84c9 | |||
b3e4d81362 | |||
b9bb093600 | |||
d0b7d37cd8 | |||
6ff6f12fec | |||
a4a7f4cad1 |
86 changed files with 17048 additions and 2024 deletions
|
@ -93,7 +93,6 @@ pipeline:
|
||||||
MIX_ENV: prod
|
MIX_ENV: prod
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
commands:
|
commands:
|
||||||
- rm config/emoji.txt
|
|
||||||
- apt-get update && apt-get install -y cmake libmagic-dev rclone zip imagemagick libmagic-dev git build-essential g++ wget
|
- apt-get update && apt-get install -y cmake libmagic-dev rclone zip imagemagick libmagic-dev git build-essential g++ wget
|
||||||
- *clean
|
- *clean
|
||||||
- echo "import Config" > config/prod.secret.exs
|
- echo "import Config" > config/prod.secret.exs
|
||||||
|
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -6,6 +6,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- support for fedibird-fe, and non-breaking API parity for it to function
|
||||||
|
- support for setting instance languages in metadata
|
||||||
|
- support for reusing oauth tokens, and not requiring new authorizations
|
||||||
|
- the ability to obfuscate domains in your MRF descriptions
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- MFM parsing is now done on the backend by a modified version of ilja's parser -> https://akkoma.dev/AkkomaGang/mfm-parser
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Compatibility with latest meilisearch
|
||||||
|
- Resolution of nested mix tasks (i.e search.meilisearch) in OTP releases
|
||||||
|
- Elasticsearch returning likes and repeats, displaying as posts
|
||||||
|
- Ensure key generation happens at registration-time to prevent potential race-conditions
|
||||||
|
- Ensured websockets get closed on logout
|
||||||
|
- Allowed GoToSocial-style `?query_string` signatures
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Non-finch HTTP adapters. `:tesla, :adapter` is now highly recommended to be set to the default.
|
||||||
|
|
||||||
|
## 2022.08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- extended runtime module support, see config cheatsheet
|
- extended runtime module support, see config cheatsheet
|
||||||
- quote posting; quotes are limited to public posts
|
- quote posting; quotes are limited to public posts
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
*a smallish microblogging platform, aka the cooler pleroma*
|
*a smallish microblogging platform, aka the cooler pleroma*
|
||||||
|
|
||||||
|
![English OK](https://img.shields.io/badge/English-OK-blueviolet) ![日本語OK](https://img.shields.io/badge/%E6%97%A5%E6%9C%AC%E8%AA%9E-OK-blueviolet)
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
This is a fork of Pleroma, which is a microblogging server software that can federate (= exchange messages with) other servers that support ActivityPub. What that means is that you can host a server for yourself or your friends and stay in control of your online identity, but still exchange messages with people on larger servers. Akkoma will federate with all servers that implement ActivityPub, like Friendica, GNU Social, Hubzilla, Mastodon, Misskey, Peertube, and Pixelfed.
|
This is a fork of Pleroma, which is a microblogging server software that can federate (= exchange messages with) other servers that support ActivityPub. What that means is that you can host a server for yourself or your friends and stay in control of your online identity, but still exchange messages with people on larger servers. Akkoma will federate with all servers that implement ActivityPub, like Friendica, GNU Social, Hubzilla, Mastodon, Misskey, Peertube, and Pixelfed.
|
||||||
|
|
|
@ -197,6 +197,7 @@
|
||||||
avatar_upload_limit: 2_000_000,
|
avatar_upload_limit: 2_000_000,
|
||||||
background_upload_limit: 4_000_000,
|
background_upload_limit: 4_000_000,
|
||||||
banner_upload_limit: 4_000_000,
|
banner_upload_limit: 4_000_000,
|
||||||
|
languages: ["en"],
|
||||||
poll_limits: %{
|
poll_limits: %{
|
||||||
max_options: 20,
|
max_options: 20,
|
||||||
max_option_chars: 200,
|
max_option_chars: 200,
|
||||||
|
@ -734,6 +735,14 @@
|
||||||
"build_dir" => "distribution",
|
"build_dir" => "distribution",
|
||||||
"ref" => "akkoma"
|
"ref" => "akkoma"
|
||||||
},
|
},
|
||||||
|
"fedibird-fe" => %{
|
||||||
|
"name" => "fedibird-fe",
|
||||||
|
"git" => "https://akkoma.dev/AkkomaGang/fedibird-fe",
|
||||||
|
"build_url" =>
|
||||||
|
"https://akkoma-updates.s3-website.fr-par.scw.cloud/frontend/${ref}/fedibird-fe.zip",
|
||||||
|
"build_dir" => "distribution",
|
||||||
|
"ref" => "akkoma"
|
||||||
|
},
|
||||||
"admin-fe" => %{
|
"admin-fe" => %{
|
||||||
"name" => "admin-fe",
|
"name" => "admin-fe",
|
||||||
"git" => "https://akkoma.dev/AkkomaGang/admin-fe",
|
"git" => "https://akkoma.dev/AkkomaGang/admin-fe",
|
||||||
|
@ -746,7 +755,7 @@
|
||||||
"git" => "https://gitlab.com/soapbox-pub/soapbox-fe",
|
"git" => "https://gitlab.com/soapbox-pub/soapbox-fe",
|
||||||
"build_url" =>
|
"build_url" =>
|
||||||
"https://gitlab.com/soapbox-pub/soapbox-fe/-/jobs/artifacts/${ref}/download?job=build-production",
|
"https://gitlab.com/soapbox-pub/soapbox-fe/-/jobs/artifacts/${ref}/download?job=build-production",
|
||||||
"ref" => "v1.0.0",
|
"ref" => "v2.0.0",
|
||||||
"build_dir" => "static"
|
"build_dir" => "static"
|
||||||
},
|
},
|
||||||
# For developers - enables a swagger frontend to view the openapi spec
|
# For developers - enables a swagger frontend to view the openapi spec
|
||||||
|
@ -785,7 +794,8 @@
|
||||||
config :pleroma, :mrf,
|
config :pleroma, :mrf,
|
||||||
policies: [Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy, Pleroma.Web.ActivityPub.MRF.TagPolicy],
|
policies: [Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy, Pleroma.Web.ActivityPub.MRF.TagPolicy],
|
||||||
transparency: true,
|
transparency: true,
|
||||||
transparency_exclusions: []
|
transparency_exclusions: [],
|
||||||
|
transparency_obfuscate_domains: []
|
||||||
|
|
||||||
config :ex_aws, http_client: Pleroma.HTTP.ExAws
|
config :ex_aws, http_client: Pleroma.HTTP.ExAws
|
||||||
|
|
||||||
|
|
|
@ -509,6 +509,16 @@
|
||||||
"Pleroma"
|
"Pleroma"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
key: :languages,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: "Languages the instance uses",
|
||||||
|
suggestions: [
|
||||||
|
"en",
|
||||||
|
"ja",
|
||||||
|
"fr"
|
||||||
|
]
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
key: :email,
|
key: :email,
|
||||||
label: "Admin Email Address",
|
label: "Admin Email Address",
|
||||||
|
@ -1169,7 +1179,6 @@
|
||||||
hideFilteredStatuses: false,
|
hideFilteredStatuses: false,
|
||||||
hideMutedPosts: false,
|
hideMutedPosts: false,
|
||||||
hidePostStats: false,
|
hidePostStats: false,
|
||||||
hideSitename: false,
|
|
||||||
hideUserStats: false,
|
hideUserStats: false,
|
||||||
loginMethod: "password",
|
loginMethod: "password",
|
||||||
logo: "/static/logo.svg",
|
logo: "/static/logo.svg",
|
||||||
|
@ -1235,12 +1244,6 @@
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Hide notices statistics (repeats, favorites, ...)"
|
description: "Hide notices statistics (repeats, favorites, ...)"
|
||||||
},
|
},
|
||||||
%{
|
|
||||||
key: :hideSitename,
|
|
||||||
label: "Hide Sitename",
|
|
||||||
type: :boolean,
|
|
||||||
description: "Hides instance name from PleromaFE banner"
|
|
||||||
},
|
|
||||||
%{
|
%{
|
||||||
key: :hideUserStats,
|
key: :hideUserStats,
|
||||||
label: "Hide user stats",
|
label: "Hide user stats",
|
||||||
|
@ -1350,6 +1353,42 @@
|
||||||
type: :string,
|
type: :string,
|
||||||
description: "Which theme to use. Available themes are defined in styles.json",
|
description: "Which theme to use. Available themes are defined in styles.json",
|
||||||
suggestions: ["pleroma-dark"]
|
suggestions: ["pleroma-dark"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :showPanelNavShortcuts,
|
||||||
|
label: "Show timeline panel nav shortcuts",
|
||||||
|
type: :boolean,
|
||||||
|
description: "Whether to put timeline nav tabs on the top of the panel"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :showNavShortcuts,
|
||||||
|
label: "Show navbar shortcuts",
|
||||||
|
type: :boolean,
|
||||||
|
description: "Whether to put extra navigation options on the navbar"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :showWiderShortcuts,
|
||||||
|
label: "Increase navbar shortcut spacing",
|
||||||
|
type: :boolean,
|
||||||
|
description: "Whether to add extra space between navbar icons"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :hideSiteFavicon,
|
||||||
|
label: "Hide site favicon",
|
||||||
|
type: :boolean,
|
||||||
|
description: "Whether to hide the instance favicon from the navbar"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :hideSiteName,
|
||||||
|
label: "Hide site name",
|
||||||
|
type: :boolean,
|
||||||
|
description: "Whether to hide the site name from the navbar"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :renderMisskeyMarkdown,
|
||||||
|
label: "Render misskey markdown",
|
||||||
|
type: :boolean,
|
||||||
|
description: "Whether to render Misskey-flavoured markdown"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1442,13 +1481,14 @@
|
||||||
%{
|
%{
|
||||||
key: :theme_color,
|
key: :theme_color,
|
||||||
type: :string,
|
type: :string,
|
||||||
description: "Describe the theme color of the app",
|
description: "Describe the theme color of the app - this is only used for mastodon-fe",
|
||||||
suggestions: ["#282c37", "mediumpurple"]
|
suggestions: ["#282c37", "mediumpurple"]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :background_color,
|
key: :background_color,
|
||||||
type: :string,
|
type: :string,
|
||||||
description: "Describe the background color of the app",
|
description:
|
||||||
|
"Describe the background color of the app - this is only used for mastodon-fe",
|
||||||
suggestions: ["#191b22", "aliceblue"]
|
suggestions: ["#191b22", "aliceblue"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -2597,9 +2637,10 @@
|
||||||
%{
|
%{
|
||||||
key: :proxy_url,
|
key: :proxy_url,
|
||||||
label: "Proxy URL",
|
label: "Proxy URL",
|
||||||
type: [:string, :tuple],
|
type: :string,
|
||||||
description: "Proxy URL",
|
description:
|
||||||
suggestions: ["localhost:9020", {:socks5, :localhost, 3090}]
|
"Proxy URL - of the format http://host:port. Advise setting in .exs instead of admin-fe due to this being set at boot-time.",
|
||||||
|
suggestions: ["http://localhost:3128"]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :user_agent,
|
key: :user_agent,
|
||||||
|
|
|
@ -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
|
|
|
@ -120,6 +120,7 @@ To add configuration to your config file, you can copy it from the base config.
|
||||||
* `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)).
|
* `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)).
|
||||||
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
|
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
|
||||||
* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
|
* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
|
||||||
|
* `transparency_obfuscate_domains`: Show domains with `*` in the middle, to censor them if needed. For example, `ridingho.me` will show as `rid*****.me`
|
||||||
|
|
||||||
## Federation
|
## Federation
|
||||||
### MRF policies
|
### MRF policies
|
||||||
|
@ -283,14 +284,19 @@ config :pleroma, :frontends,
|
||||||
"name" => "swagger-ui",
|
"name" => "swagger-ui",
|
||||||
"ref" => "stable",
|
"ref" => "stable",
|
||||||
"enabled" => true
|
"enabled" => true
|
||||||
|
},
|
||||||
|
mastodon: %{
|
||||||
|
"name" => "mastodon-fe",
|
||||||
|
"ref" => "akkoma"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* `:primary` - The frontend that will be served at `/`
|
* `:primary` - The frontend that will be served at `/`
|
||||||
* `:admin` - The frontend that will be served at `/pleroma/admin`
|
* `:admin` - The frontend that will be served at `/pleroma/admin`
|
||||||
* `:swagger` - Config for developers to act as an API reference to be served at `/akkoma/swaggerui/` (trailing slash _needed_). Disabled by default.
|
* `:swagger` - Config for developers to act as an API reference to be served at `/akkoma/swaggerui/` (trailing slash _needed_). Disabled by default.
|
||||||
|
* `:mastodon` - The mastodon-fe configuration. This shouldn't need to be changed. This is served at `/web` when installed.
|
||||||
|
|
||||||
### :static_fe
|
### :static\_fe
|
||||||
|
|
||||||
Render profiles and posts using server-generated HTML that is viewable without using JavaScript.
|
Render profiles and posts using server-generated HTML that is viewable without using JavaScript.
|
||||||
|
|
||||||
|
@ -516,7 +522,7 @@ Available caches:
|
||||||
|
|
||||||
### :http
|
### :http
|
||||||
|
|
||||||
* `proxy_url`: an upstream proxy to fetch posts and/or media with, (default: `nil`)
|
* `proxy_url`: an upstream proxy to fetch posts and/or media with, (default: `nil`); for example `http://127.0.0.1:3192`. Does not support SOCKS5 proxy, only http(s).
|
||||||
* `send_user_agent`: should we include a user agent with HTTP requests? (default: `true`)
|
* `send_user_agent`: should we include a user agent with HTTP requests? (default: `true`)
|
||||||
* `user_agent`: what user agent should we use? (default: `:default`), must be string or `:default`
|
* `user_agent`: what user agent should we use? (default: `:default`), must be string or `:default`
|
||||||
* `adapter`: array of adapter options
|
* `adapter`: array of adapter options
|
||||||
|
|
|
@ -19,6 +19,10 @@ config :pleroma, :frontends,
|
||||||
admin: %{
|
admin: %{
|
||||||
"name" => "admin-fe",
|
"name" => "admin-fe",
|
||||||
"ref" => "stable"
|
"ref" => "stable"
|
||||||
|
},
|
||||||
|
mastodon: %{
|
||||||
|
"name" => "mastodon-fe",
|
||||||
|
"ref" => "akkoma"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -26,12 +30,18 @@ This would serve the frontend from the the folder at `$instance_static/frontends
|
||||||
|
|
||||||
Refer to [the frontend CLI task](../../administration/CLI_tasks/frontend) for how to install the frontend's files
|
Refer to [the frontend CLI task](../../administration/CLI_tasks/frontend) for how to install the frontend's files
|
||||||
|
|
||||||
If you wish masto-fe to also be enabled, you will also need to run the install task for `mastodon-fe`. Not doing this will lead to the frontend not working.
|
|
||||||
|
|
||||||
If you choose not to install a frontend for whatever reason, it is recommended that you enable [`:static_fe`](#static_fe) to allow remote users to click "view remote source". Don't bother with this if you've got no unauthenticated access though.
|
If you choose not to install a frontend for whatever reason, it is recommended that you enable [`:static_fe`](#static_fe) to allow remote users to click "view remote source". Don't bother with this if you've got no unauthenticated access though.
|
||||||
|
|
||||||
You can also replace the default "no frontend" page by placing an `index.html` file under your `instance/static/` directory.
|
You can also replace the default "no frontend" page by placing an `index.html` file under your `instance/static/` directory.
|
||||||
|
|
||||||
|
## Mastodon-FE
|
||||||
|
|
||||||
|
Akkoma supports both [glitchsoc](https://github.com/glitch-soc/mastodon)'s more "vanilla" mastodon frontend,
|
||||||
|
as well as [fedibird](https://github.com/fedibird/mastodon)'s extended frontend which has near-feature-parity with akkoma (with quoting and reactions).
|
||||||
|
|
||||||
|
To enable either one, you must run the `frontend.install` task for either `mastodon-fe` or `fedibird-fe` (both `--ref akkoma`), then make sure
|
||||||
|
`:pleroma, :frontends, :mastodon` references the one you want.
|
||||||
|
|
||||||
## Swagger (openAPI) documentation viewer
|
## Swagger (openAPI) documentation viewer
|
||||||
|
|
||||||
If you're a developer and you'd like a human-readable rendering of the
|
If you're a developer and you'd like a human-readable rendering of the
|
||||||
|
|
|
@ -23,7 +23,15 @@ def start_pleroma do
|
||||||
Pleroma.Config.Oban.warn()
|
Pleroma.Config.Oban.warn()
|
||||||
Pleroma.Application.limiters_setup()
|
Pleroma.Application.limiters_setup()
|
||||||
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
|
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
|
||||||
Finch.start_link(name: MyFinch)
|
|
||||||
|
proxy_url = Pleroma.Config.get([:http, :proxy_url])
|
||||||
|
proxy = Pleroma.HTTP.AdapterHelper.format_proxy(proxy_url)
|
||||||
|
|
||||||
|
finch_config =
|
||||||
|
[:http, :adapter]
|
||||||
|
|> Pleroma.Config.get([])
|
||||||
|
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy_pool(proxy)
|
||||||
|
|> Keyword.put(:name, MyFinch)
|
||||||
|
|
||||||
unless System.get_env("DEBUG") do
|
unless System.get_env("DEBUG") do
|
||||||
Logger.remove_backend(:console)
|
Logger.remove_backend(:console)
|
||||||
|
@ -45,6 +53,7 @@ def start_pleroma do
|
||||||
Pleroma.Emoji,
|
Pleroma.Emoji,
|
||||||
{Pleroma.Config.TransferTask, false},
|
{Pleroma.Config.TransferTask, false},
|
||||||
Pleroma.Web.Endpoint,
|
Pleroma.Web.Endpoint,
|
||||||
|
{Finch, finch_config},
|
||||||
{Oban, oban_config},
|
{Oban, oban_config},
|
||||||
{Majic.Pool,
|
{Majic.Pool,
|
||||||
[name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]}
|
[name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]}
|
||||||
|
|
|
@ -9,7 +9,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
import Pleroma.Search.Meilisearch,
|
import Pleroma.Search.Meilisearch,
|
||||||
only: [meili_post: 2, meili_put: 2, meili_get: 1, meili_delete!: 1]
|
only: [meili_put: 2, meili_get: 1, meili_delete!: 1]
|
||||||
|
|
||||||
def run(["index"]) do
|
def run(["index"]) do
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
@ -27,7 +27,7 @@ def run(["index"]) do
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, _} =
|
{:ok, _} =
|
||||||
meili_post(
|
meili_put(
|
||||||
"/indexes/objects/settings/ranking-rules",
|
"/indexes/objects/settings/ranking-rules",
|
||||||
[
|
[
|
||||||
"published:desc",
|
"published:desc",
|
||||||
|
@ -41,7 +41,7 @@ def run(["index"]) do
|
||||||
)
|
)
|
||||||
|
|
||||||
{:ok, _} =
|
{:ok, _} =
|
||||||
meili_post(
|
meili_put(
|
||||||
"/indexes/objects/settings/searchable-attributes",
|
"/indexes/objects/settings/searchable-attributes",
|
||||||
[
|
[
|
||||||
"content"
|
"content"
|
||||||
|
@ -91,7 +91,7 @@ def run(["index"]) do
|
||||||
)
|
)
|
||||||
|
|
||||||
with {:ok, res} <- result do
|
with {:ok, res} <- result do
|
||||||
if not Map.has_key?(res, "uid") do
|
if not Map.has_key?(res, "indexUid") do
|
||||||
IO.puts("\nFailed to index: #{inspect(result)}")
|
IO.puts("\nFailed to index: #{inspect(result)}")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
|
@ -258,6 +258,25 @@ def run(["untag", nickname | tags]) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def run(["refetch_public_keys"]) do
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
Pleroma.User.Query.build(%{
|
||||||
|
external: true,
|
||||||
|
is_active: true
|
||||||
|
})
|
||||||
|
|> refetch_public_keys()
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(["refetch_public_keys" | rest]) do
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
Pleroma.User.Query.build(%{
|
||||||
|
ap_id: rest
|
||||||
|
})
|
||||||
|
|> refetch_public_keys()
|
||||||
|
end
|
||||||
|
|
||||||
def run(["invite" | rest]) do
|
def run(["invite" | rest]) do
|
||||||
{options, [], []} =
|
{options, [], []} =
|
||||||
OptionParser.parse(rest,
|
OptionParser.parse(rest,
|
||||||
|
@ -519,6 +538,26 @@ def run(["fix_follow_state", local_user, remote_user]) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp refetch_public_keys(query) do
|
||||||
|
query
|
||||||
|
|> Pleroma.Repo.chunk_stream(50, :batches)
|
||||||
|
|> Stream.each(fn users ->
|
||||||
|
users
|
||||||
|
|> Enum.each(fn user ->
|
||||||
|
IO.puts("Re-Resolving: #{user.ap_id}")
|
||||||
|
|
||||||
|
with {:ok, user} <- Pleroma.User.fetch_by_ap_id(user.ap_id),
|
||||||
|
changeset <- Pleroma.User.update_changeset(user),
|
||||||
|
{:ok, _user} <- Pleroma.User.update_and_set_cache(changeset) do
|
||||||
|
:ok
|
||||||
|
else
|
||||||
|
error -> IO.puts("Could not resolve: #{user.ap_id}, #{inspect(error)}")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|> Stream.run()
|
||||||
|
end
|
||||||
|
|
||||||
defp set_moderator(user, value) do
|
defp set_moderator(user, value) do
|
||||||
{:ok, user} =
|
{:ok, user} =
|
||||||
user
|
user
|
||||||
|
|
|
@ -63,7 +63,8 @@ def start(_type, _args) do
|
||||||
Pleroma.Repo,
|
Pleroma.Repo,
|
||||||
Config.TransferTask,
|
Config.TransferTask,
|
||||||
Pleroma.Emoji,
|
Pleroma.Emoji,
|
||||||
Pleroma.Web.Plugs.RateLimiter.Supervisor
|
Pleroma.Web.Plugs.RateLimiter.Supervisor,
|
||||||
|
{Task.Supervisor, name: Pleroma.TaskSupervisor}
|
||||||
] ++
|
] ++
|
||||||
cachex_children() ++
|
cachex_children() ++
|
||||||
http_children() ++
|
http_children() ++
|
||||||
|
@ -248,9 +249,13 @@ def limiters_setup do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp http_children do
|
defp http_children do
|
||||||
|
proxy_url = Config.get([:http, :proxy_url])
|
||||||
|
proxy = Pleroma.HTTP.AdapterHelper.format_proxy(proxy_url)
|
||||||
|
|
||||||
config =
|
config =
|
||||||
[:http, :adapter]
|
[:http, :adapter]
|
||||||
|> Config.get([])
|
|> Config.get([])
|
||||||
|
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy_pool(proxy)
|
||||||
|> Keyword.put(:name, MyFinch)
|
|> Keyword.put(:name, MyFinch)
|
||||||
|
|
||||||
[{Finch, config}]
|
[{Finch, config}]
|
||||||
|
|
|
@ -11,10 +11,7 @@ defmodule Akkoma.Collections.Fetcher do
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
def fetch_collection_by_ap_id(ap_id) when is_binary(ap_id) do
|
@spec fetch_collection(String.t() | map()) :: {:ok, [Pleroma.Object.t()]} | {:error, any()}
|
||||||
fetch_collection(ap_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch_collection(ap_id) when is_binary(ap_id) do
|
def fetch_collection(ap_id) when is_binary(ap_id) do
|
||||||
with {:ok, page} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do
|
with {:ok, page} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do
|
||||||
{:ok, objects_from_collection(page)}
|
{:ok, objects_from_collection(page)}
|
||||||
|
@ -26,7 +23,7 @@ def fetch_collection(ap_id) when is_binary(ap_id) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_collection(%{"type" => type} = page)
|
def fetch_collection(%{"type" => type} = page)
|
||||||
when type in ["Collection", "OrderedCollection"] do
|
when type in ["Collection", "OrderedCollection", "CollectionPage", "OrderedCollectionPage"] do
|
||||||
{:ok, objects_from_collection(page)}
|
{:ok, objects_from_collection(page)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -38,12 +35,13 @@ defp items_in_page(%{"type" => type, "items" => items})
|
||||||
when is_list(items) and type in ["Collection", "CollectionPage"],
|
when is_list(items) and type in ["Collection", "CollectionPage"],
|
||||||
do: items
|
do: items
|
||||||
|
|
||||||
defp objects_from_collection(%{"type" => "OrderedCollection", "orderedItems" => items})
|
defp objects_from_collection(%{"type" => type, "orderedItems" => items} = page)
|
||||||
when is_list(items),
|
when is_list(items) and type in ["OrderedCollection", "OrderedCollectionPage"],
|
||||||
do: items
|
do: maybe_next_page(page, items)
|
||||||
|
|
||||||
defp objects_from_collection(%{"type" => "Collection", "items" => items}) when is_list(items),
|
defp objects_from_collection(%{"type" => type, "items" => items} = page)
|
||||||
do: items
|
when is_list(items) and type in ["Collection", "CollectionPage"],
|
||||||
|
do: maybe_next_page(page, items)
|
||||||
|
|
||||||
defp objects_from_collection(%{"type" => type, "first" => first})
|
defp objects_from_collection(%{"type" => type, "first" => first})
|
||||||
when is_binary(first) and type in ["Collection", "OrderedCollection"] do
|
when is_binary(first) and type in ["Collection", "OrderedCollection"] do
|
||||||
|
@ -55,11 +53,13 @@ defp objects_from_collection(%{"type" => type, "first" => %{"id" => id}})
|
||||||
fetch_page_items(id)
|
fetch_page_items(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp objects_from_collection(_page), do: []
|
||||||
|
|
||||||
defp fetch_page_items(id, items \\ []) do
|
defp fetch_page_items(id, items \\ []) do
|
||||||
if Enum.count(items) >= Config.get([:activitypub, :max_collection_objects]) do
|
if Enum.count(items) >= Config.get([:activitypub, :max_collection_objects]) do
|
||||||
items
|
items
|
||||||
else
|
else
|
||||||
{:ok, page} = Fetcher.fetch_and_contain_remote_object_from_id(id)
|
with {:ok, page} <- Fetcher.fetch_and_contain_remote_object_from_id(id) do
|
||||||
objects = items_in_page(page)
|
objects = items_in_page(page)
|
||||||
|
|
||||||
if Enum.count(objects) > 0 do
|
if Enum.count(objects) > 0 do
|
||||||
|
@ -67,6 +67,14 @@ defp fetch_page_items(id, items \\ []) do
|
||||||
else
|
else
|
||||||
items
|
items
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
{:error, "Object has been deleted"} ->
|
||||||
|
items
|
||||||
|
|
||||||
|
{:error, error} ->
|
||||||
|
Logger.error("Could not fetch page #{id} - #{inspect(error)}")
|
||||||
|
{:error, error}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Helpers.AuthHelper do
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
|
||||||
@oauth_token_session_key :oauth_token
|
@oauth_token_session_key :oauth_token
|
||||||
|
@oauth_user_session_key :oauth_user
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Skips OAuth permissions (scopes) checks, assigns nil `:token`.
|
Skips OAuth permissions (scopes) checks, assigns nil `:token`.
|
||||||
|
@ -43,4 +44,16 @@ def put_session_token(%Conn{} = conn, token) when is_binary(token) do
|
||||||
def delete_session_token(%Conn{} = conn) do
|
def delete_session_token(%Conn{} = conn) do
|
||||||
delete_session(conn, @oauth_token_session_key)
|
delete_session(conn, @oauth_token_session_key)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def put_session_user(%Conn{} = conn, user) do
|
||||||
|
put_session(conn, @oauth_user_session_key, user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_session_user(%Conn{} = conn) do
|
||||||
|
delete_session(conn, @oauth_user_session_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_session_user(%Conn{} = conn) do
|
||||||
|
get_session(conn, @oauth_user_session_key)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.HTTP.AdapterHelper do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Configure Tesla.Client with default and customized adapter options.
|
Configure Tesla.Client with default and customized adapter options.
|
||||||
"""
|
"""
|
||||||
@defaults [name: MyFinch, connect_timeout: 5_000, recv_timeout: 5_000]
|
@defaults [name: MyFinch, pool_timeout: 5_000, receive_timeout: 5_000]
|
||||||
|
|
||||||
@type proxy_type() :: :socks4 | :socks5
|
@type proxy_type() :: :socks4 | :socks5
|
||||||
@type host() :: charlist() | :inet.ip_address()
|
@type host() :: charlist() | :inet.ip_address()
|
||||||
|
@ -25,15 +25,58 @@ def format_proxy(nil), do: nil
|
||||||
|
|
||||||
def format_proxy(proxy_url) do
|
def format_proxy(proxy_url) do
|
||||||
case parse_proxy(proxy_url) do
|
case parse_proxy(proxy_url) do
|
||||||
{:ok, host, port} -> {host, port}
|
{:ok, host, port} -> {:http, host, port, []}
|
||||||
{:ok, type, host, port} -> {type, host, port}
|
{:ok, type, host, port} -> {type, host, port, []}
|
||||||
_ -> nil
|
_ -> nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec maybe_add_proxy(keyword(), proxy() | nil) :: keyword()
|
@spec maybe_add_proxy(keyword(), proxy() | nil) :: keyword()
|
||||||
def maybe_add_proxy(opts, nil), do: opts
|
def maybe_add_proxy(opts, nil), do: opts
|
||||||
def maybe_add_proxy(opts, proxy), do: Keyword.put_new(opts, :proxy, proxy)
|
|
||||||
|
def maybe_add_proxy(opts, proxy) do
|
||||||
|
Keyword.put(opts, :proxy, proxy)
|
||||||
|
end
|
||||||
|
|
||||||
|
def maybe_add_proxy_pool(opts, nil), do: opts
|
||||||
|
|
||||||
|
def maybe_add_proxy_pool(opts, proxy) do
|
||||||
|
Logger.info("Using HTTP Proxy: #{inspect(proxy)}")
|
||||||
|
|
||||||
|
opts
|
||||||
|
|> maybe_add_pools()
|
||||||
|
|> maybe_add_default_pool()
|
||||||
|
|> maybe_add_conn_opts()
|
||||||
|
|> put_in([:pools, :default, :conn_opts, :proxy], proxy)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_add_pools(opts) do
|
||||||
|
if Keyword.has_key?(opts, :pools) do
|
||||||
|
opts
|
||||||
|
else
|
||||||
|
Keyword.put(opts, :pools, %{})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_add_default_pool(opts) do
|
||||||
|
pools = Keyword.get(opts, :pools)
|
||||||
|
|
||||||
|
if Map.has_key?(pools, :default) do
|
||||||
|
opts
|
||||||
|
else
|
||||||
|
put_in(opts, [:pools, :default], [])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_add_conn_opts(opts) do
|
||||||
|
conn_opts = get_in(opts, [:pools, :default, :conn_opts])
|
||||||
|
|
||||||
|
unless is_nil(conn_opts) do
|
||||||
|
opts
|
||||||
|
else
|
||||||
|
put_in(opts, [:pools, :default, :conn_opts], [])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Merge default connection & adapter options with received ones.
|
Merge default connection & adapter options with received ones.
|
||||||
|
@ -46,36 +89,31 @@ def options(%URI{} = uri, opts \\ []) do
|
||||||
|> AdapterHelper.Default.options(uri)
|
|> AdapterHelper.Default.options(uri)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp proxy_type("http"), do: {:ok, :http}
|
||||||
|
defp proxy_type("https"), do: {:ok, :https}
|
||||||
|
defp proxy_type(_), do: {:error, :unknown}
|
||||||
|
|
||||||
@spec parse_proxy(String.t() | tuple() | nil) ::
|
@spec parse_proxy(String.t() | tuple() | nil) ::
|
||||||
{:ok, host(), pos_integer()}
|
{:ok, host(), pos_integer()}
|
||||||
| {:ok, proxy_type(), host(), pos_integer()}
|
| {:ok, proxy_type(), host(), pos_integer()}
|
||||||
| {:error, atom()}
|
| {:error, atom()}
|
||||||
| nil
|
| nil
|
||||||
|
|
||||||
def parse_proxy(nil), do: nil
|
def parse_proxy(nil), do: nil
|
||||||
|
|
||||||
def parse_proxy(proxy) when is_binary(proxy) do
|
def parse_proxy(proxy) when is_binary(proxy) do
|
||||||
with [host, port] <- String.split(proxy, ":"),
|
with %URI{} = uri <- URI.parse(proxy),
|
||||||
{port, ""} <- Integer.parse(port) do
|
{:ok, type} <- proxy_type(uri.scheme) do
|
||||||
{:ok, parse_host(host), port}
|
{:ok, type, uri.host, uri.port}
|
||||||
else
|
else
|
||||||
{_, _} ->
|
e ->
|
||||||
Logger.warn("Parsing port failed #{inspect(proxy)}")
|
Logger.warn("Parsing proxy failed #{inspect(proxy)}, #{inspect(e)}")
|
||||||
{:error, :invalid_proxy_port}
|
|
||||||
|
|
||||||
:error ->
|
|
||||||
Logger.warn("Parsing port failed #{inspect(proxy)}")
|
|
||||||
{:error, :invalid_proxy_port}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
Logger.warn("Parsing proxy failed #{inspect(proxy)}")
|
|
||||||
{:error, :invalid_proxy}
|
{:error, :invalid_proxy}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_proxy(proxy) when is_tuple(proxy) do
|
def parse_proxy(proxy) when is_tuple(proxy) do
|
||||||
with {type, host, port} <- proxy do
|
with {type, host, port} <- proxy do
|
||||||
{:ok, type, parse_host(host), port}
|
{:ok, type, host, port}
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
Logger.warn("Parsing proxy failed #{inspect(proxy)}")
|
Logger.warn("Parsing proxy failed #{inspect(proxy)}")
|
||||||
|
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Default do
|
||||||
|
|
||||||
@spec options(keyword(), URI.t()) :: keyword()
|
@spec options(keyword(), URI.t()) :: keyword()
|
||||||
def options(opts, _uri) do
|
def options(opts, _uri) do
|
||||||
proxy = Pleroma.Config.get([:http, :proxy_url], nil)
|
proxy = Pleroma.Config.get([:http, :proxy_url])
|
||||||
AdapterHelper.maybe_add_proxy(opts, AdapterHelper.format_proxy(proxy))
|
AdapterHelper.maybe_add_proxy(opts, AdapterHelper.format_proxy(proxy))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -25,7 +25,7 @@ defp mix_task(task, args) do
|
||||||
module = Module.split(module)
|
module = Module.split(module)
|
||||||
|
|
||||||
match?(["Mix", "Tasks", "Pleroma" | _], module) and
|
match?(["Mix", "Tasks", "Pleroma" | _], module) and
|
||||||
String.downcase(List.last(module)) == task
|
task_match?(module, task)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if module do
|
if module do
|
||||||
|
@ -35,6 +35,13 @@ defp mix_task(task, args) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp task_match?(["Mix", "Tasks", "Pleroma" | module_path], task) do
|
||||||
|
module_path
|
||||||
|
|> Enum.join(".")
|
||||||
|
|> String.downcase()
|
||||||
|
|> String.equivalent?(String.downcase(task))
|
||||||
|
end
|
||||||
|
|
||||||
def migrate(args) do
|
def migrate(args) do
|
||||||
Mix.Tasks.Pleroma.Ecto.Migrate.run(args)
|
Mix.Tasks.Pleroma.Ecto.Migrate.run(args)
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,7 +23,7 @@ def es_query(:activity, query, offset, limit) do
|
||||||
timeout: "5s",
|
timeout: "5s",
|
||||||
sort: [
|
sort: [
|
||||||
"_score",
|
"_score",
|
||||||
%{_timestamp: %{order: "desc", format: "basic_date_time"}}
|
%{"_timestamp" => %{order: "desc", format: "basic_date_time"}}
|
||||||
],
|
],
|
||||||
query: %{
|
query: %{
|
||||||
bool: %{
|
bool: %{
|
||||||
|
@ -62,8 +62,12 @@ def search(user, query, options) do
|
||||||
Task.async(fn ->
|
Task.async(fn ->
|
||||||
q = es_query(:activity, parsed_query, offset, limit)
|
q = es_query(:activity, parsed_query, offset, limit)
|
||||||
|
|
||||||
Pleroma.Search.Elasticsearch.Store.search(:activities, q)
|
:activities
|
||||||
|> Enum.filter(fn x -> Visibility.visible_for_user?(x, user) end)
|
|> Pleroma.Search.Elasticsearch.Store.search(q)
|
||||||
|
|> Enum.filter(fn x ->
|
||||||
|
x.data["type"] == "Create" && x.object.data["type"] == "Note" &&
|
||||||
|
Visibility.visible_for_user?(x, user)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
activity_results = Task.await(activity_task)
|
activity_results = Task.await(activity_task)
|
||||||
|
|
|
@ -42,7 +42,6 @@ def search(:activities, q) do
|
||||||
results
|
results
|
||||||
|> Enum.map(fn result -> result["_id"] end)
|
|> Enum.map(fn result -> result["_id"] end)
|
||||||
|> Pleroma.Activity.all_by_ids_with_object()
|
|> Pleroma.Activity.all_by_ids_with_object()
|
||||||
|> Enum.sort(&(&1.inserted_at >= &2.inserted_at))
|
|
||||||
else
|
else
|
||||||
e ->
|
e ->
|
||||||
Logger.error(e)
|
Logger.error(e)
|
||||||
|
|
|
@ -681,6 +681,7 @@ def register_changeset_ldap(struct, params = %{password: password})
|
||||||
|> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
|
|> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
|
||||||
|> validate_format(:nickname, local_nickname_regex())
|
|> validate_format(:nickname, local_nickname_regex())
|
||||||
|> put_ap_id()
|
|> put_ap_id()
|
||||||
|
|> put_keys()
|
||||||
|> unique_constraint(:ap_id)
|
|> unique_constraint(:ap_id)
|
||||||
|> put_following_and_follower_and_featured_address()
|
|> put_following_and_follower_and_featured_address()
|
||||||
end
|
end
|
||||||
|
@ -740,6 +741,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
|> validate_length(:registration_reason, max: reason_limit)
|
|> validate_length(:registration_reason, max: reason_limit)
|
||||||
|> maybe_validate_required_email(opts[:external])
|
|> maybe_validate_required_email(opts[:external])
|
||||||
|> put_password_hash
|
|> put_password_hash
|
||||||
|
|> put_keys()
|
||||||
|> put_ap_id()
|
|> put_ap_id()
|
||||||
|> unique_constraint(:ap_id)
|
|> unique_constraint(:ap_id)
|
||||||
|> put_following_and_follower_and_featured_address()
|
|> put_following_and_follower_and_featured_address()
|
||||||
|
@ -755,6 +757,11 @@ def maybe_validate_required_email(changeset, _) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def put_keys(changeset) do
|
||||||
|
{:ok, pem} = Keys.generate_rsa_pem()
|
||||||
|
put_change(changeset, :keys, pem)
|
||||||
|
end
|
||||||
|
|
||||||
def put_ap_id(changeset) do
|
def put_ap_id(changeset) do
|
||||||
ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
|
ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
|
||||||
put_change(changeset, :ap_id, ap_id)
|
put_change(changeset, :ap_id, ap_id)
|
||||||
|
|
|
@ -41,6 +41,16 @@ defmodule Pleroma.Web.ActivityPub.MRF do
|
||||||
suggestions: [
|
suggestions: [
|
||||||
"exclusion.com"
|
"exclusion.com"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :transparency_obfuscate_domains,
|
||||||
|
label: "MRF domain obfuscation",
|
||||||
|
type: {:list, :string},
|
||||||
|
description:
|
||||||
|
"Obfuscate domains in MRF transparency. This is useful if the domain you're blocking contains words you don't want displayed, but still want to disclose the MRF settings.",
|
||||||
|
suggestions: [
|
||||||
|
"badword.com"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,10 +256,35 @@ def filter(object) when is_binary(object) do
|
||||||
|
|
||||||
def filter(object), do: {:ok, object}
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
defp obfuscate(string) when is_binary(string) do
|
||||||
|
string
|
||||||
|
|> to_charlist()
|
||||||
|
|> Enum.with_index()
|
||||||
|
|> Enum.map(fn
|
||||||
|
{?., _index} ->
|
||||||
|
?.
|
||||||
|
|
||||||
|
{char, index} ->
|
||||||
|
if 3 <= index && index < String.length(string) - 3, do: ?*, else: char
|
||||||
|
end)
|
||||||
|
|> to_string()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_obfuscate(host, obfuscations) do
|
||||||
|
if MRF.subdomain_match?(obfuscations, host) do
|
||||||
|
obfuscate(host)
|
||||||
|
else
|
||||||
|
host
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def describe do
|
def describe do
|
||||||
exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples()
|
exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples()
|
||||||
|
|
||||||
|
obfuscations =
|
||||||
|
Config.get([:mrf, :transparency_obfuscate_domains], []) |> MRF.subdomains_regex()
|
||||||
|
|
||||||
mrf_simple_excluded =
|
mrf_simple_excluded =
|
||||||
Config.get(:mrf_simple)
|
Config.get(:mrf_simple)
|
||||||
|> Enum.map(fn {rule, instances} ->
|
|> Enum.map(fn {rule, instances} ->
|
||||||
|
@ -269,7 +294,7 @@ def describe do
|
||||||
mrf_simple =
|
mrf_simple =
|
||||||
mrf_simple_excluded
|
mrf_simple_excluded
|
||||||
|> Enum.map(fn {rule, instances} ->
|
|> Enum.map(fn {rule, instances} ->
|
||||||
{rule, Enum.map(instances, fn {host, _} -> host end)}
|
{rule, Enum.map(instances, fn {host, _} -> maybe_obfuscate(host, obfuscations) end)}
|
||||||
end)
|
end)
|
||||||
|> Map.new()
|
|> Map.new()
|
||||||
|
|
||||||
|
@ -286,7 +311,9 @@ def describe do
|
||||||
|> Enum.map(fn {rule, instances} ->
|
|> Enum.map(fn {rule, instances} ->
|
||||||
instances =
|
instances =
|
||||||
instances
|
instances
|
||||||
|> Enum.map(fn {host, reason} -> {host, %{"reason" => reason}} end)
|
|> Enum.map(fn {host, reason} ->
|
||||||
|
{maybe_obfuscate(host, obfuscations), %{"reason" => reason}}
|
||||||
|
end)
|
||||||
|> Map.new()
|
|> Map.new()
|
||||||
|
|
||||||
{rule, instances}
|
{rule, instances}
|
||||||
|
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
alias Pleroma.Object.Fetcher
|
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
@ -58,19 +57,10 @@ defp fix_tag(%{"tag" => tag} = data) when is_list(tag), do: data
|
||||||
defp fix_tag(%{"tag" => tag} = data) when is_map(tag), do: Map.put(data, "tag", [tag])
|
defp fix_tag(%{"tag" => tag} = data) when is_map(tag), do: Map.put(data, "tag", [tag])
|
||||||
defp fix_tag(data), do: Map.drop(data, ["tag"])
|
defp fix_tag(data), do: Map.drop(data, ["tag"])
|
||||||
|
|
||||||
defp fix_replies(%{"replies" => %{"first" => %{"items" => replies}}} = data)
|
defp fix_replies(%{"replies" => replies} = data) when is_list(replies), do: data
|
||||||
when is_list(replies),
|
|
||||||
do: Map.put(data, "replies", replies)
|
|
||||||
|
|
||||||
defp fix_replies(%{"replies" => %{"items" => replies}} = data) when is_list(replies),
|
|
||||||
do: Map.put(data, "replies", replies)
|
|
||||||
|
|
||||||
defp fix_replies(%{"replies" => replies} = data) when is_bitstring(replies),
|
|
||||||
do: Map.drop(data, ["replies"])
|
|
||||||
|
|
||||||
defp fix_replies(%{"replies" => %{"first" => first}} = data) do
|
defp fix_replies(%{"replies" => %{"first" => first}} = data) do
|
||||||
with {:ok, %{"orderedItems" => replies}} <-
|
with {:ok, replies} <- Akkoma.Collections.Fetcher.fetch_collection(first) do
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(first) do
|
|
||||||
Map.put(data, "replies", replies)
|
Map.put(data, "replies", replies)
|
||||||
else
|
else
|
||||||
{:error, _} ->
|
{:error, _} ->
|
||||||
|
@ -79,7 +69,10 @@ defp fix_replies(%{"replies" => %{"first" => first}} = data) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp fix_replies(data), do: data
|
defp fix_replies(%{"replies" => %{"items" => replies}} = data) when is_list(replies),
|
||||||
|
do: Map.put(data, "replies", replies)
|
||||||
|
|
||||||
|
defp fix_replies(data), do: Map.delete(data, "replies")
|
||||||
|
|
||||||
defp remote_mention_resolver(
|
defp remote_mention_resolver(
|
||||||
%{"id" => ap_id, "tag" => tags},
|
%{"id" => ap_id, "tag" => tags},
|
||||||
|
@ -108,6 +101,8 @@ defp remote_mention_resolver(
|
||||||
end
|
end
|
||||||
|
|
||||||
# https://github.com/misskey-dev/misskey/pull/8787
|
# https://github.com/misskey-dev/misskey/pull/8787
|
||||||
|
# Misskey has an awful tendency to drop all custom formatting when it sends remotely
|
||||||
|
# So this basically reprocesses their MFM source
|
||||||
defp fix_misskey_content(
|
defp fix_misskey_content(
|
||||||
%{"source" => %{"mediaType" => "text/x.misskeymarkdown", "content" => content}} = object
|
%{"source" => %{"mediaType" => "text/x.misskeymarkdown", "content" => content}} = object
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,8 +7,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Object.Containment
|
alias Pleroma.Object.Containment
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
def cast_and_filter_recipients(message, field, follower_collection, field_fallback \\ []) do
|
def cast_and_filter_recipients(message, field, follower_collection, field_fallback \\ []) do
|
||||||
{:ok, data} = ObjectValidators.Recipients.cast(message[field] || field_fallback)
|
{:ok, data} = ObjectValidators.Recipients.cast(message[field] || field_fallback)
|
||||||
|
@ -32,7 +32,7 @@ def fix_object_defaults(data) do
|
||||||
|> cast_and_filter_recipients("cc", follower_collection)
|
|> cast_and_filter_recipients("cc", follower_collection)
|
||||||
|> cast_and_filter_recipients("bto", follower_collection)
|
|> cast_and_filter_recipients("bto", follower_collection)
|
||||||
|> cast_and_filter_recipients("bcc", follower_collection)
|
|> cast_and_filter_recipients("bcc", follower_collection)
|
||||||
|> Transmogrifier.fix_implicit_addressing(follower_collection)
|
|> fix_implicit_addressing(follower_collection)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_activity_addressing(activity) do
|
def fix_activity_addressing(activity) do
|
||||||
|
@ -43,7 +43,7 @@ def fix_activity_addressing(activity) do
|
||||||
|> cast_and_filter_recipients("cc", follower_collection)
|
|> cast_and_filter_recipients("cc", follower_collection)
|
||||||
|> cast_and_filter_recipients("bto", follower_collection)
|
|> cast_and_filter_recipients("bto", follower_collection)
|
||||||
|> cast_and_filter_recipients("bcc", follower_collection)
|
|> cast_and_filter_recipients("bcc", follower_collection)
|
||||||
|> Transmogrifier.fix_implicit_addressing(follower_collection)
|
|> fix_implicit_addressing(follower_collection)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_actor(data) do
|
def fix_actor(data) do
|
||||||
|
@ -73,4 +73,27 @@ def fix_object_action_recipients(data, %Object{data: %{"actor" => actor}}) do
|
||||||
|
|
||||||
Map.put(data, "to", to)
|
Map.put(data, "to", to)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# if as:Public is addressed, then make sure the followers collection is also addressed
|
||||||
|
# so that the activities will be delivered to local users.
|
||||||
|
def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do
|
||||||
|
recipients = to ++ cc
|
||||||
|
|
||||||
|
if followers_collection not in recipients do
|
||||||
|
cond do
|
||||||
|
Pleroma.Constants.as_public() in cc ->
|
||||||
|
to = to ++ [followers_collection]
|
||||||
|
Map.put(object, "to", to)
|
||||||
|
|
||||||
|
Pleroma.Constants.as_public() in to ->
|
||||||
|
cc = cc ++ [followers_collection]
|
||||||
|
Map.put(object, "cc", cc)
|
||||||
|
|
||||||
|
true ->
|
||||||
|
object
|
||||||
|
end
|
||||||
|
else
|
||||||
|
object
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@ -67,7 +66,7 @@ defp fix_addressing(data, object) do
|
||||||
|> CommonFixes.cast_and_filter_recipients("cc", follower_collection, object["cc"])
|
|> CommonFixes.cast_and_filter_recipients("cc", follower_collection, object["cc"])
|
||||||
|> CommonFixes.cast_and_filter_recipients("bto", follower_collection, object["bto"])
|
|> CommonFixes.cast_and_filter_recipients("bto", follower_collection, object["bto"])
|
||||||
|> CommonFixes.cast_and_filter_recipients("bcc", follower_collection, object["bcc"])
|
|> CommonFixes.cast_and_filter_recipients("bcc", follower_collection, object["bcc"])
|
||||||
|> Transmogrifier.fix_implicit_addressing(follower_collection)
|
|> CommonFixes.fix_implicit_addressing(follower_collection)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix(data, meta) do
|
def fix(data, meta) do
|
||||||
|
|
|
@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
alias Pleroma.Web.ActivityPub.Pipeline
|
alias Pleroma.Web.ActivityPub.Pipeline
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
alias Pleroma.Web.Federator
|
alias Pleroma.Web.Federator
|
||||||
alias Pleroma.Workers.TransmogrifierWorker
|
alias Pleroma.Workers.TransmogrifierWorker
|
||||||
|
|
||||||
|
@ -95,29 +96,6 @@ def fix_explicit_addressing(%{"to" => to, "cc" => cc} = object, follower_collect
|
||||||
|> Map.put("cc", final_cc)
|
|> Map.put("cc", final_cc)
|
||||||
end
|
end
|
||||||
|
|
||||||
# if as:Public is addressed, then make sure the followers collection is also addressed
|
|
||||||
# so that the activities will be delivered to local users.
|
|
||||||
def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do
|
|
||||||
recipients = to ++ cc
|
|
||||||
|
|
||||||
if followers_collection not in recipients do
|
|
||||||
cond do
|
|
||||||
Pleroma.Constants.as_public() in cc ->
|
|
||||||
to = to ++ [followers_collection]
|
|
||||||
Map.put(object, "to", to)
|
|
||||||
|
|
||||||
Pleroma.Constants.as_public() in to ->
|
|
||||||
cc = cc ++ [followers_collection]
|
|
||||||
Map.put(object, "cc", cc)
|
|
||||||
|
|
||||||
true ->
|
|
||||||
object
|
|
||||||
end
|
|
||||||
else
|
|
||||||
object
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def fix_addressing(object) do
|
def fix_addressing(object) do
|
||||||
{:ok, %User{follower_address: follower_collection}} =
|
{:ok, %User{follower_address: follower_collection}} =
|
||||||
object
|
object
|
||||||
|
@ -130,7 +108,7 @@ def fix_addressing(object) do
|
||||||
|> fix_addressing_list("bto")
|
|> fix_addressing_list("bto")
|
||||||
|> fix_addressing_list("bcc")
|
|> fix_addressing_list("bcc")
|
||||||
|> fix_explicit_addressing(follower_collection)
|
|> fix_explicit_addressing(follower_collection)
|
||||||
|> fix_implicit_addressing(follower_collection)
|
|> CommonFixes.fix_implicit_addressing(follower_collection)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_actor(%{"attributedTo" => actor} = object) do
|
def fix_actor(%{"attributedTo" => actor} = object) do
|
||||||
|
|
|
@ -152,9 +152,15 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
||||||
description:
|
description:
|
||||||
"A map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`"
|
"A map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`"
|
||||||
},
|
},
|
||||||
|
context: %Schema{
|
||||||
|
type: :string,
|
||||||
|
description: "The thread identifier the status is associated with"
|
||||||
|
},
|
||||||
conversation_id: %Schema{
|
conversation_id: %Schema{
|
||||||
type: :integer,
|
type: :integer,
|
||||||
description: "The ID of the AP context the status is associated with (if any)"
|
deprecated: true,
|
||||||
|
description:
|
||||||
|
"The ID of the AP context the status is associated with (if any); deprecated, please use `context` instead"
|
||||||
},
|
},
|
||||||
direct_conversation_id: %Schema{
|
direct_conversation_id: %Schema{
|
||||||
type: :integer,
|
type: :integer,
|
||||||
|
@ -356,6 +362,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
||||||
"pinned" => false,
|
"pinned" => false,
|
||||||
"pleroma" => %{
|
"pleroma" => %{
|
||||||
"content" => %{"text/plain" => "foobar"},
|
"content" => %{"text/plain" => "foobar"},
|
||||||
|
"context" => "http://localhost:4001/objects/8b4c0c80-6a37-4d2a-b1b9-05a19e3875aa",
|
||||||
"conversation_id" => 345_972,
|
"conversation_id" => 345_972,
|
||||||
"direct_conversation_id" => nil,
|
"direct_conversation_id" => nil,
|
||||||
"emoji_reactions" => [],
|
"emoji_reactions" => [],
|
||||||
|
|
|
@ -285,11 +285,11 @@ def format_input(text, "text/html", options) do
|
||||||
|
|
||||||
def format_input(text, "text/x.misskeymarkdown", options) do
|
def format_input(text, "text/x.misskeymarkdown", options) do
|
||||||
text
|
text
|
||||||
|
|> Formatter.markdown_to_html()
|
||||||
|
|> MfmParser.Parser.parse()
|
||||||
|
|> MfmParser.Encoder.to_html()
|
||||||
|> Formatter.linkify(options)
|
|> Formatter.linkify(options)
|
||||||
|> Formatter.html_escape("text/x.misskeymarkdown")
|
|> Formatter.html_escape("text/html")
|
||||||
|> (fn {text, mentions, tags} ->
|
|
||||||
{String.replace(text, ~r/\r?\n/, "<br>"), mentions, tags}
|
|
||||||
end).()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_input(text, "text/markdown", options) do
|
def format_input(text, "text/markdown", options) do
|
||||||
|
|
|
@ -27,9 +27,21 @@ defmodule Pleroma.Web.MastoFEController do
|
||||||
def index(conn, _params) do
|
def index(conn, _params) do
|
||||||
with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn,
|
with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn,
|
||||||
{:ok, %{id: ^token_app_id}} <- AuthController.local_mastofe_app() do
|
{:ok, %{id: ^token_app_id}} <- AuthController.local_mastofe_app() do
|
||||||
|
flavour =
|
||||||
|
[:frontends, :mastodon]
|
||||||
|
|> Pleroma.Config.get()
|
||||||
|
|> Map.get("name", "mastodon-fe")
|
||||||
|
|
||||||
|
index =
|
||||||
|
if flavour == "fedibird-fe" do
|
||||||
|
"fedibird.index.html"
|
||||||
|
else
|
||||||
|
"glitchsoc.index.html"
|
||||||
|
end
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_layout(false)
|
|> put_layout(false)
|
||||||
|> render("index.html",
|
|> render(index,
|
||||||
token: token.token,
|
token: token.token,
|
||||||
user: user,
|
user: user,
|
||||||
custom_emojis: Pleroma.Emoji.get_all()
|
custom_emojis: Pleroma.Emoji.get_all()
|
||||||
|
|
|
@ -27,7 +27,8 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
|
||||||
def login(conn, %{"code" => auth_token} = params) do
|
def login(conn, %{"code" => auth_token} = params) do
|
||||||
with {:ok, app} <- local_mastofe_app(),
|
with {:ok, app} <- local_mastofe_app(),
|
||||||
{:ok, auth} <- Authorization.get_by_token(app, auth_token),
|
{:ok, auth} <- Authorization.get_by_token(app, auth_token),
|
||||||
{:ok, oauth_token} <- Token.exchange_token(app, auth) do
|
%User{} = user <- User.get_cached_by_id(auth.user_id),
|
||||||
|
{:ok, oauth_token} <- Token.get_or_exchange_token(auth, app, user) do
|
||||||
redirect_to =
|
redirect_to =
|
||||||
conn
|
conn
|
||||||
|> local_mastodon_post_login_path()
|
|> local_mastodon_post_login_path()
|
||||||
|
|
|
@ -26,7 +26,7 @@ def render("show.json", _) do
|
||||||
thumbnail:
|
thumbnail:
|
||||||
URI.merge(Pleroma.Web.Endpoint.url(), Keyword.get(instance, :instance_thumbnail))
|
URI.merge(Pleroma.Web.Endpoint.url(), Keyword.get(instance, :instance_thumbnail))
|
||||||
|> to_string,
|
|> to_string,
|
||||||
languages: ["en"],
|
languages: Keyword.get(instance, :languages, ["en"]),
|
||||||
registrations: Keyword.get(instance, :registrations_open),
|
registrations: Keyword.get(instance, :registrations_open),
|
||||||
approval_required: Keyword.get(instance, :account_approval_required),
|
approval_required: Keyword.get(instance, :account_approval_required),
|
||||||
# Extra (not present in Mastodon):
|
# Extra (not present in Mastodon):
|
||||||
|
|
|
@ -57,8 +57,19 @@ defp get_replied_to_activities(activities) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_context_id(%{data: %{"context" => context}}) when is_binary(context),
|
# DEPRECATED This field seems to be a left-over from the StatusNet era.
|
||||||
do: :erlang.crc32(context)
|
# If your application uses `pleroma.conversation_id`: this field is deprecated.
|
||||||
|
# It is currently stubbed instead by doing a CRC32 of the context, and
|
||||||
|
# clearing the MSB to avoid overflow exceptions with signed integers on the
|
||||||
|
# different clients using this field (Java/Kotlin code, mostly; see Husky.)
|
||||||
|
# This should be removed in a future version of Pleroma. Pleroma-FE currently
|
||||||
|
# depends on this field, as well.
|
||||||
|
defp get_context_id(%{data: %{"context" => context}}) when is_binary(context) do
|
||||||
|
use Bitwise
|
||||||
|
|
||||||
|
:erlang.crc32(context)
|
||||||
|
|> band(bnot(0x8000_0000))
|
||||||
|
end
|
||||||
|
|
||||||
defp get_context_id(_), do: nil
|
defp get_context_id(_), do: nil
|
||||||
|
|
||||||
|
@ -364,9 +375,11 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
emojis: build_emojis(object.data["emoji"]),
|
emojis: build_emojis(object.data["emoji"]),
|
||||||
quote_id: if(quote, do: quote.id, else: nil),
|
quote_id: if(quote, do: quote.id, else: nil),
|
||||||
quote: maybe_render_quote(quote, opts),
|
quote: maybe_render_quote(quote, opts),
|
||||||
|
emoji_reactions: emoji_reactions,
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
local: activity.local,
|
local: activity.local,
|
||||||
conversation_id: get_context_id(activity),
|
conversation_id: get_context_id(activity),
|
||||||
|
context: object.data["context"],
|
||||||
in_reply_to_account_acct: reply_to_user && reply_to_user.nickname,
|
in_reply_to_account_acct: reply_to_user && reply_to_user.nickname,
|
||||||
content: %{"text/plain" => content_plaintext},
|
content: %{"text/plain" => content_plaintext},
|
||||||
spoiler_text: %{"text/plain" => summary},
|
spoiler_text: %{"text/plain" => summary},
|
||||||
|
@ -577,7 +590,8 @@ defp build_emoji_map(emoji, users, url, current_user) do
|
||||||
name: emoji,
|
name: emoji,
|
||||||
count: length(users),
|
count: length(users),
|
||||||
url: MediaProxy.url(url),
|
url: MediaProxy.url(url),
|
||||||
me: !!(current_user && current_user.ap_id in users)
|
me: !!(current_user && current_user.ap_id in users),
|
||||||
|
account_ids: Enum.map(users, fn user -> User.get_cached_by_ap_id(user).id end)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -609,15 +623,19 @@ defp build_image_url(_, _), do: nil
|
||||||
defp maybe_render_quote(nil, _), do: nil
|
defp maybe_render_quote(nil, _), do: nil
|
||||||
|
|
||||||
defp maybe_render_quote(quote, opts) do
|
defp maybe_render_quote(quote, opts) do
|
||||||
if opts[:do_not_recurse] || !visible_for_user?(quote, opts[:for]) do
|
with %User{} = quoted_user <- User.get_cached_by_ap_id(quote.actor),
|
||||||
nil
|
false <- Map.get(opts, :do_not_recurse, false),
|
||||||
else
|
true <- visible_for_user?(quote, opts[:for]),
|
||||||
|
false <- User.blocks?(opts[:for], quoted_user),
|
||||||
|
false <- User.mutes?(opts[:for], quoted_user) do
|
||||||
opts =
|
opts =
|
||||||
opts
|
opts
|
||||||
|> Map.put(:activity, quote)
|
|> Map.put(:activity, quote)
|
||||||
|> Map.put(:do_not_recurse, true)
|
|> Map.put(:do_not_recurse, true)
|
||||||
|
|
||||||
render("show.json", opts)
|
render("show.json", opts)
|
||||||
|
else
|
||||||
|
_ -> nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,8 +32,15 @@ def init(%{qs: qs} = req, state) do
|
||||||
req
|
req
|
||||||
end
|
end
|
||||||
|
|
||||||
{:cowboy_websocket, req, %{user: user, topic: topic, count: 0, timer: nil},
|
{:cowboy_websocket, req,
|
||||||
%{idle_timeout: @timeout}}
|
%{
|
||||||
|
user: user,
|
||||||
|
topic: topic,
|
||||||
|
count: 0,
|
||||||
|
timer: nil,
|
||||||
|
subscriptions: [],
|
||||||
|
oauth_token: oauth_token
|
||||||
|
}, %{idle_timeout: @timeout}}
|
||||||
else
|
else
|
||||||
{:error, :bad_topic} ->
|
{:error, :bad_topic} ->
|
||||||
Logger.debug("#{__MODULE__} bad topic #{inspect(req)}")
|
Logger.debug("#{__MODULE__} bad topic #{inspect(req)}")
|
||||||
|
@ -52,7 +59,7 @@ def websocket_init(state) do
|
||||||
"#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic}"
|
"#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic}"
|
||||||
)
|
)
|
||||||
|
|
||||||
Streamer.add_socket(state.topic, state.user)
|
Streamer.add_socket(state.topic, state.oauth_token)
|
||||||
{:ok, %{state | timer: timer()}}
|
{:ok, %{state | timer: timer()}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -65,21 +72,50 @@ def websocket_handle(:pong, state) do
|
||||||
# We only receive pings for now
|
# We only receive pings for now
|
||||||
def websocket_handle(:ping, state), do: {:ok, state}
|
def websocket_handle(:ping, state), do: {:ok, state}
|
||||||
|
|
||||||
def websocket_handle({:text, "ping"}, state) do
|
def websocket_handle({:text, ping}, state) when ping in ~w[ping PING] do
|
||||||
if state.timer, do: Process.cancel_timer(state.timer)
|
if state.timer, do: Process.cancel_timer(state.timer)
|
||||||
{:reply, {:text, "pong"}, %{state | timer: timer()}}
|
{:reply, {:text, "pong"}, %{state | timer: timer()}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def websocket_handle({:text, text}, state) do
|
||||||
|
with {:ok, json} <- Jason.decode(text) do
|
||||||
|
websocket_handle({:json, json}, state)
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
Logger.error("#{__MODULE__} received text frame: #{text}")
|
||||||
|
{:ok, state}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def websocket_handle(
|
||||||
|
{:json, %{"type" => "subscribe", "stream" => stream_name}},
|
||||||
|
%{user: user, oauth_token: token} = state
|
||||||
|
) do
|
||||||
|
with {:ok, topic} <- Streamer.get_topic(stream_name, user, token, %{}) do
|
||||||
|
new_subscriptions =
|
||||||
|
[topic | Map.get(state, :subscriptions, [])]
|
||||||
|
|> Enum.uniq()
|
||||||
|
|
||||||
|
{:ok, _topic} = Streamer.add_socket(topic, user)
|
||||||
|
|
||||||
|
{:ok, Map.put(state, :subscriptions, new_subscriptions)}
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
Logger.error("#{__MODULE__} received invalid topic: #{stream_name}")
|
||||||
|
{:ok, state}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def websocket_handle(frame, state) do
|
def websocket_handle(frame, state) do
|
||||||
Logger.error("#{__MODULE__} received frame: #{inspect(frame)}")
|
Logger.error("#{__MODULE__} received frame: #{inspect(frame)}")
|
||||||
{:ok, state}
|
{:ok, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
def websocket_info({:render_with_user, view, template, item}, state) do
|
def websocket_info({:render_with_user, view, template, item, topic}, state) do
|
||||||
user = %User{} = User.get_cached_by_ap_id(state.user.ap_id)
|
user = %User{} = User.get_cached_by_ap_id(state.user.ap_id)
|
||||||
|
|
||||||
unless Streamer.filtered_by_user?(user, item) do
|
unless Streamer.filtered_by_user?(user, item) do
|
||||||
websocket_info({:text, view.render(template, item, user)}, %{state | user: user})
|
websocket_info({:text, view.render(template, item, user, topic)}, %{state | user: user})
|
||||||
else
|
else
|
||||||
{:ok, state}
|
{:ok, state}
|
||||||
end
|
end
|
||||||
|
@ -103,6 +139,10 @@ def websocket_info(:tick, state) do
|
||||||
{:reply, :ping, %{state | timer: nil, count: 0}, :hibernate}
|
{:reply, :ping, %{state | timer: nil, count: 0}, :hibernate}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def websocket_info(:close, state) do
|
||||||
|
{:stop, state}
|
||||||
|
end
|
||||||
|
|
||||||
# State can be `[]` only in case we terminate before switching to websocket,
|
# State can be `[]` only in case we terminate before switching to websocket,
|
||||||
# we already log errors for these cases in `init/1`, so just do nothing here
|
# we already log errors for these cases in `init/1`, so just do nothing here
|
||||||
def terminate(_reason, _req, []), do: :ok
|
def terminate(_reason, _req, []), do: :ok
|
||||||
|
|
|
@ -94,4 +94,9 @@ def get_by_token(%App{id: app_id} = _app, token) do
|
||||||
from(t in __MODULE__, where: t.app_id == ^app_id and t.token == ^token)
|
from(t in __MODULE__, where: t.app_id == ^app_id and t.token == ^token)
|
||||||
|> Repo.find_resource()
|
|> Repo.find_resource()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_preeexisting_by_app_and_user(%App{id: app_id} = _app, %User{id: user_id} = _user) do
|
||||||
|
from(t in __MODULE__, where: t.app_id == ^app_id and t.user_id == ^user_id, limit: 1)
|
||||||
|
|> Repo.find_resource()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -59,18 +59,39 @@ def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, %{"force_login" =>
|
||||||
# after user already authorized to MastodonFE.
|
# after user already authorized to MastodonFE.
|
||||||
# So we have to check client and token.
|
# So we have to check client and token.
|
||||||
def authorize(
|
def authorize(
|
||||||
%Plug.Conn{assigns: %{token: %Token{} = token}} = conn,
|
%Plug.Conn{assigns: %{token: %Token{} = token, user: %User{} = user}} = conn,
|
||||||
%{"client_id" => client_id} = params
|
%{"client_id" => client_id} = params
|
||||||
) do
|
) do
|
||||||
with %Token{} = t <- Repo.get_by(Token, token: token.token) |> Repo.preload(:app),
|
with %Token{} = t <- Repo.get_by(Token, token: token.token) |> Repo.preload(:app),
|
||||||
^client_id <- t.app.client_id do
|
^client_id <- t.app.client_id do
|
||||||
handle_existing_authorization(conn, params)
|
handle_existing_authorization(conn, params)
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
maybe_reuse_token(conn, params, user.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize(%Plug.Conn{} = conn, params) do
|
||||||
|
# if we have a user in the session, attempt to authenticate as them
|
||||||
|
# otherwise show the login form
|
||||||
|
maybe_reuse_token(conn, params, AuthHelper.get_session_user(conn))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_reuse_token(conn, params, user_id) when is_binary(user_id) do
|
||||||
|
with %User{} = user <- User.get_cached_by_id(user_id),
|
||||||
|
%App{} = app <- Repo.get_by(App, client_id: params["client_id"]),
|
||||||
|
{:ok, %Token{} = token} <- Token.get_preeexisting_by_app_and_user(app, user),
|
||||||
|
{:ok, %Authorization{} = auth} <-
|
||||||
|
Authorization.get_preeexisting_by_app_and_user(app, user) do
|
||||||
|
conn
|
||||||
|
|> assign(:token, token)
|
||||||
|
|> after_create_authorization(auth, %{"authorization" => params})
|
||||||
else
|
else
|
||||||
_ -> do_authorize(conn, params)
|
_ -> do_authorize(conn, params)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorize(%Plug.Conn{} = conn, params), do: do_authorize(conn, params)
|
defp maybe_reuse_token(conn, params, _user), do: do_authorize(conn, params)
|
||||||
|
|
||||||
defp do_authorize(%Plug.Conn{} = conn, params) do
|
defp do_authorize(%Plug.Conn{} = conn, params) do
|
||||||
app = Repo.get_by(App, client_id: params["client_id"])
|
app = Repo.get_by(App, client_id: params["client_id"])
|
||||||
|
@ -148,7 +169,9 @@ def create_authorization(%Plug.Conn{assigns: %{user: %User{} = user}} = conn, pa
|
||||||
def create_authorization(%Plug.Conn{} = conn, %{"authorization" => _} = params, opts) do
|
def create_authorization(%Plug.Conn{} = conn, %{"authorization" => _} = params, opts) do
|
||||||
with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]),
|
with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]),
|
||||||
{:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do
|
{:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do
|
||||||
after_create_authorization(conn, auth, params)
|
conn
|
||||||
|
|> AuthHelper.put_session_user(user.id)
|
||||||
|
|> after_create_authorization(auth, params)
|
||||||
else
|
else
|
||||||
error ->
|
error ->
|
||||||
handle_create_authorization_error(conn, error, params)
|
handle_create_authorization_error(conn, error, params)
|
||||||
|
@ -269,7 +292,7 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"}
|
||||||
fixed_token = Token.Utils.fix_padding(params["code"]),
|
fixed_token = Token.Utils.fix_padding(params["code"]),
|
||||||
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
|
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
|
||||||
%User{} = user <- User.get_cached_by_id(auth.user_id),
|
%User{} = user <- User.get_cached_by_id(auth.user_id),
|
||||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
{:ok, token} <- Token.get_or_exchange_token(auth, app, user) do
|
||||||
after_token_exchange(conn, %{user: user, token: token})
|
after_token_exchange(conn, %{user: user, token: token})
|
||||||
else
|
else
|
||||||
error ->
|
error ->
|
||||||
|
@ -321,6 +344,7 @@ def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
|
||||||
def after_token_exchange(%Plug.Conn{} = conn, %{token: token} = view_params) do
|
def after_token_exchange(%Plug.Conn{} = conn, %{token: token} = view_params) do
|
||||||
conn
|
conn
|
||||||
|> AuthHelper.put_session_token(token.token)
|
|> AuthHelper.put_session_token(token.token)
|
||||||
|
|> AuthHelper.put_session_user(token.user_id)
|
||||||
|> json(OAuthView.render("token.json", view_params))
|
|> json(OAuthView.render("token.json", view_params))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,16 @@ def exchange_token(app, auth) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_preeexisting_by_app_and_user(app, user) do
|
||||||
|
Query.get_by_app(app.id)
|
||||||
|
|> Query.get_by_user(user.id)
|
||||||
|
|> Query.get_unexpired()
|
||||||
|
|> Query.preload([:user])
|
||||||
|
|> Query.sort_by_inserted_at()
|
||||||
|
|> Query.limit(1)
|
||||||
|
|> Repo.find_resource()
|
||||||
|
end
|
||||||
|
|
||||||
defp put_token(changeset) do
|
defp put_token(changeset) do
|
||||||
changeset
|
changeset
|
||||||
|> change(%{token: Token.Utils.generate_token()})
|
|> change(%{token: Token.Utils.generate_token()})
|
||||||
|
@ -86,6 +96,14 @@ defp put_refresh_token(changeset, attrs) do
|
||||||
|> unique_constraint(:refresh_token)
|
|> unique_constraint(:refresh_token)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_or_exchange_token(%Authorization{} = auth, %App{} = app, %User{} = user) do
|
||||||
|
if auth.used do
|
||||||
|
get_preeexisting_by_app_and_user(app, user)
|
||||||
|
else
|
||||||
|
exchange_token(app, auth)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp put_valid_until(changeset, attrs) do
|
defp put_valid_until(changeset, attrs) do
|
||||||
valid_until =
|
valid_until =
|
||||||
Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), lifespan()))
|
Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), lifespan()))
|
||||||
|
|
|
@ -38,6 +38,19 @@ def get_by_user(query \\ Token, user_id) do
|
||||||
from(q in query, where: q.user_id == ^user_id)
|
from(q in query, where: q.user_id == ^user_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_unexpired(query) do
|
||||||
|
now = NaiveDateTime.utc_now()
|
||||||
|
from(q in query, where: q.valid_until > ^now)
|
||||||
|
end
|
||||||
|
|
||||||
|
def limit(query, limit) do
|
||||||
|
from(q in query, limit: ^limit)
|
||||||
|
end
|
||||||
|
|
||||||
|
def sort_by_inserted_at(query) do
|
||||||
|
from(q in query, order_by: [desc: :updated_at])
|
||||||
|
end
|
||||||
|
|
||||||
@spec preload(query, any) :: query
|
@spec preload(query, any) :: query
|
||||||
def preload(query \\ Token, assoc_preload \\ [])
|
def preload(query \\ Token, assoc_preload \\ [])
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,18 @@ def revoke(%App{} = app, %{"token" => token} = _attrs) do
|
||||||
@doc "Revokes access token"
|
@doc "Revokes access token"
|
||||||
@spec revoke(Token.t()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()}
|
@spec revoke(Token.t()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()}
|
||||||
def revoke(%Token{} = token) do
|
def revoke(%Token{} = token) do
|
||||||
Repo.delete(token)
|
with {:ok, token} <- Repo.delete(token) do
|
||||||
|
Task.Supervisor.start_child(
|
||||||
|
Pleroma.TaskSupervisor,
|
||||||
|
Pleroma.Web.Streamer,
|
||||||
|
:close_streams_by_oauth_token,
|
||||||
|
[token],
|
||||||
|
restart: :transient
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, token}
|
||||||
|
else
|
||||||
|
result -> result
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -74,6 +74,8 @@ defp filter(reactions, %{emoji: emoji}) when is_binary(emoji) do
|
||||||
defp filter(reactions, _), do: reactions
|
defp filter(reactions, _), do: reactions
|
||||||
|
|
||||||
def create(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do
|
def create(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do
|
||||||
|
emoji = Pleroma.Emoji.maybe_quote(emoji)
|
||||||
|
|
||||||
with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do
|
with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do
|
||||||
activity = Activity.get_by_id(activity_id)
|
activity = Activity.get_by_id(activity_id)
|
||||||
|
|
||||||
|
|
|
@ -27,11 +27,11 @@ def call(conn, _opts) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def route_aliases(%{path_info: ["objects", id]} = conn) do
|
def route_aliases(%{path_info: ["objects", id], query_string: query_string}) do
|
||||||
ap_id = Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :object, id)
|
ap_id = Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :object, id)
|
||||||
|
|
||||||
with %Activity{} = activity <- Activity.get_by_object_ap_id_with_object(ap_id) do
|
with %Activity{} = activity <- Activity.get_by_object_ap_id_with_object(ap_id) do
|
||||||
["/notice/#{activity.id}"]
|
["/notice/#{activity.id}", "/notice/#{activity.id}?#{query_string}"]
|
||||||
else
|
else
|
||||||
_ -> []
|
_ -> []
|
||||||
end
|
end
|
||||||
|
@ -64,7 +64,9 @@ defp maybe_assign_valid_signature(conn) do
|
||||||
if has_signature_header?(conn) do
|
if has_signature_header?(conn) do
|
||||||
# set (request-target) header to the appropriate value
|
# set (request-target) header to the appropriate value
|
||||||
# we also replace the digest header with the one we computed
|
# we also replace the digest header with the one we computed
|
||||||
possible_paths = route_aliases(conn) ++ [conn.request_path]
|
possible_paths =
|
||||||
|
route_aliases(conn) ++ [conn.request_path, conn.request_path <> "?#{conn.query_string}"]
|
||||||
|
|
||||||
assign_valid_signature_on_route_aliases(conn, possible_paths)
|
assign_valid_signature_on_route_aliases(conn, possible_paths)
|
||||||
else
|
else
|
||||||
Logger.debug("No signature header!")
|
Logger.debug("No signature header!")
|
||||||
|
|
|
@ -457,6 +457,11 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/federation_status", InstancesController, :show)
|
get("/federation_status", InstancesController, :show)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scope "/api/v1", Pleroma.Web.PleromaAPI do
|
||||||
|
pipe_through(:authenticated_api)
|
||||||
|
put("/statuses/:id/emoji_reactions/:emoji", EmojiReactionController, :create)
|
||||||
|
end
|
||||||
|
|
||||||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||||
pipe_through(:authenticated_api)
|
pipe_through(:authenticated_api)
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ def registry, do: @registry
|
||||||
{:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized}
|
{:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized}
|
||||||
def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do
|
def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do
|
||||||
with {:ok, topic} <- get_topic(stream, user, oauth_token, params) do
|
with {:ok, topic} <- get_topic(stream, user, oauth_token, params) do
|
||||||
add_socket(topic, user)
|
add_socket(topic, oauth_token)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -114,15 +114,20 @@ def get_topic("list", _user, _oauth_token, _params) do
|
||||||
{:error, :unauthorized}
|
{:error, :unauthorized}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# mastodon multi-topic WS
|
||||||
|
def get_topic(nil, _user, _oauth_token, _params) do
|
||||||
|
{:ok, :multi}
|
||||||
|
end
|
||||||
|
|
||||||
def get_topic(_stream, _user, _oauth_token, _params) do
|
def get_topic(_stream, _user, _oauth_token, _params) do
|
||||||
{:error, :bad_topic}
|
{:error, :bad_topic}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Registers the process for streaming. Use `get_topic/3` to get the full authorized topic."
|
@doc "Registers the process for streaming. Use `get_topic/3` to get the full authorized topic."
|
||||||
def add_socket(topic, user) do
|
def add_socket(topic, oauth_token) do
|
||||||
if should_env_send?() do
|
if should_env_send?() do
|
||||||
auth? = if user, do: true
|
oauth_token_id = if oauth_token, do: oauth_token.id, else: false
|
||||||
Registry.register(@registry, topic, auth?)
|
Registry.register(@registry, topic, oauth_token_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, topic}
|
{:ok, topic}
|
||||||
|
@ -186,8 +191,8 @@ defp do_stream("direct", item) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_stream("follow_relationship", item) do
|
defp do_stream("follow_relationship", item) do
|
||||||
text = StreamerView.render("follow_relationships_update.json", item)
|
|
||||||
user_topic = "user:#{item.follower.id}"
|
user_topic = "user:#{item.follower.id}"
|
||||||
|
text = StreamerView.render("follow_relationships_update.json", item, user_topic)
|
||||||
|
|
||||||
Logger.debug("Trying to push follow relationship update to #{user_topic}\n\n")
|
Logger.debug("Trying to push follow relationship update to #{user_topic}\n\n")
|
||||||
|
|
||||||
|
@ -235,7 +240,7 @@ defp do_stream(topic, %Notification{} = item)
|
||||||
when topic in ["user", "user:notification"] do
|
when topic in ["user", "user:notification"] do
|
||||||
Registry.dispatch(@registry, "#{topic}:#{item.user_id}", fn list ->
|
Registry.dispatch(@registry, "#{topic}:#{item.user_id}", fn list ->
|
||||||
Enum.each(list, fn {pid, _auth} ->
|
Enum.each(list, fn {pid, _auth} ->
|
||||||
send(pid, {:render_with_user, StreamerView, "notification.json", item})
|
send(pid, {:render_with_user, StreamerView, "notification.json", item, topic})
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -259,7 +264,7 @@ defp do_stream(topic, item) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp push_to_socket(topic, %Participation{} = participation) do
|
defp push_to_socket(topic, %Participation{} = participation) do
|
||||||
rendered = StreamerView.render("conversation.json", participation)
|
rendered = StreamerView.render("conversation.json", participation, topic)
|
||||||
|
|
||||||
Registry.dispatch(@registry, topic, fn list ->
|
Registry.dispatch(@registry, topic, fn list ->
|
||||||
Enum.each(list, fn {pid, _} ->
|
Enum.each(list, fn {pid, _} ->
|
||||||
|
@ -283,12 +288,12 @@ defp push_to_socket(topic, %Activity{
|
||||||
defp push_to_socket(_topic, %Activity{data: %{"type" => "Delete"}}), do: :noop
|
defp push_to_socket(_topic, %Activity{data: %{"type" => "Delete"}}), do: :noop
|
||||||
|
|
||||||
defp push_to_socket(topic, item) do
|
defp push_to_socket(topic, item) do
|
||||||
anon_render = StreamerView.render("update.json", item)
|
anon_render = StreamerView.render("update.json", item, topic)
|
||||||
|
|
||||||
Registry.dispatch(@registry, topic, fn list ->
|
Registry.dispatch(@registry, topic, fn list ->
|
||||||
Enum.each(list, fn {pid, auth?} ->
|
Enum.each(list, fn {pid, auth?} ->
|
||||||
if auth? do
|
if auth? do
|
||||||
send(pid, {:render_with_user, StreamerView, "update.json", item})
|
send(pid, {:render_with_user, StreamerView, "update.json", item, topic})
|
||||||
else
|
else
|
||||||
send(pid, {:text, anon_render})
|
send(pid, {:text, anon_render})
|
||||||
end
|
end
|
||||||
|
@ -306,6 +311,22 @@ defp thread_containment(activity, user) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def close_streams_by_oauth_token(oauth_token) do
|
||||||
|
if should_env_send?() do
|
||||||
|
Registry.select(
|
||||||
|
@registry,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
{:"$1", :"$2", :"$3"},
|
||||||
|
[{:==, :"$3", oauth_token.id}],
|
||||||
|
[:"$2"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|> Enum.each(fn pid -> send(pid, :close) end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# In test environement, only return true if the registry is started.
|
# In test environement, only return true if the registry is started.
|
||||||
# In benchmark environment, returns false.
|
# In benchmark environment, returns false.
|
||||||
# In any other environment, always returns true.
|
# In any other environment, always returns true.
|
||||||
|
|
34
lib/pleroma/web/templates/masto_fe/fedibird.index.html.eex
Normal file
34
lib/pleroma/web/templates/masto_fe/fedibird.index.html.eex
Normal 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='{"locale":"en"}' id='mastodon'>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -14,6 +14,7 @@ def initial_state(token, user, custom_emojis) do
|
||||||
|
|
||||||
%{
|
%{
|
||||||
meta: %{
|
meta: %{
|
||||||
|
title: Config.get([:instance, :name]),
|
||||||
streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
|
streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
|
||||||
access_token: token,
|
access_token: token,
|
||||||
locale: "en",
|
locale: "en",
|
||||||
|
@ -27,7 +28,11 @@ def initial_state(token, user, custom_emojis) do
|
||||||
display_sensitive_media: false,
|
display_sensitive_media: false,
|
||||||
reduce_motion: false,
|
reduce_motion: false,
|
||||||
max_toot_chars: limit,
|
max_toot_chars: limit,
|
||||||
mascot: User.get_mascot(user)["url"]
|
mascot: User.get_mascot(user)["url"],
|
||||||
|
show_quote_button: true,
|
||||||
|
enable_reaction: true,
|
||||||
|
compact_reaction: false,
|
||||||
|
advanced_layout: true
|
||||||
},
|
},
|
||||||
poll_limits: Config.get([:instance, :poll_limits]),
|
poll_limits: Config.get([:instance, :poll_limits]),
|
||||||
rights: %{
|
rights: %{
|
||||||
|
@ -56,6 +61,7 @@ def initial_state(token, user, custom_emojis) do
|
||||||
"video\/mp4"
|
"video\/mp4"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
lists: [],
|
||||||
settings: user.mastofe_settings || %{},
|
settings: user.mastofe_settings || %{},
|
||||||
push_subscription: nil,
|
push_subscription: nil,
|
||||||
accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},
|
accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},
|
||||||
|
|
|
@ -11,8 +11,9 @@ defmodule Pleroma.Web.StreamerView do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
alias Pleroma.Web.MastodonAPI.NotificationView
|
||||||
|
|
||||||
def render("update.json", %Activity{} = activity, %User{} = user) do
|
def render("update.json", %Activity{} = activity, %User{} = user, topic) do
|
||||||
%{
|
%{
|
||||||
|
stream: [topic],
|
||||||
event: "update",
|
event: "update",
|
||||||
payload:
|
payload:
|
||||||
Pleroma.Web.MastodonAPI.StatusView.render(
|
Pleroma.Web.MastodonAPI.StatusView.render(
|
||||||
|
@ -25,8 +26,9 @@ def render("update.json", %Activity{} = activity, %User{} = user) do
|
||||||
|> Jason.encode!()
|
|> Jason.encode!()
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("notification.json", %Notification{} = notify, %User{} = user) do
|
def render("notification.json", %Notification{} = notify, %User{} = user, topic) do
|
||||||
%{
|
%{
|
||||||
|
stream: [topic],
|
||||||
event: "notification",
|
event: "notification",
|
||||||
payload:
|
payload:
|
||||||
NotificationView.render(
|
NotificationView.render(
|
||||||
|
@ -38,8 +40,9 @@ def render("notification.json", %Notification{} = notify, %User{} = user) do
|
||||||
|> Jason.encode!()
|
|> Jason.encode!()
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("update.json", %Activity{} = activity) do
|
def render("update.json", %Activity{} = activity, topic) do
|
||||||
%{
|
%{
|
||||||
|
stream: [topic],
|
||||||
event: "update",
|
event: "update",
|
||||||
payload:
|
payload:
|
||||||
Pleroma.Web.MastodonAPI.StatusView.render(
|
Pleroma.Web.MastodonAPI.StatusView.render(
|
||||||
|
@ -51,8 +54,9 @@ def render("update.json", %Activity{} = activity) do
|
||||||
|> Jason.encode!()
|
|> Jason.encode!()
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("follow_relationships_update.json", item) do
|
def render("follow_relationships_update.json", item, topic) do
|
||||||
%{
|
%{
|
||||||
|
stream: [topic],
|
||||||
event: "pleroma:follow_relationships_update",
|
event: "pleroma:follow_relationships_update",
|
||||||
payload:
|
payload:
|
||||||
%{
|
%{
|
||||||
|
@ -73,8 +77,9 @@ def render("follow_relationships_update.json", item) do
|
||||||
|> Jason.encode!()
|
|> Jason.encode!()
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("conversation.json", %Participation{} = participation) do
|
def render("conversation.json", %Participation{} = participation, topic) do
|
||||||
%{
|
%{
|
||||||
|
stream: [topic],
|
||||||
event: "conversation",
|
event: "conversation",
|
||||||
payload:
|
payload:
|
||||||
Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{
|
Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{
|
||||||
|
|
|
@ -9,6 +9,12 @@ defmodule Pleroma.Workers.ReceiverWorker do
|
||||||
|
|
||||||
@impl Oban.Worker
|
@impl Oban.Worker
|
||||||
def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
|
def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
|
||||||
Federator.perform(:incoming_ap_doc, params)
|
with {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
|
||||||
|
{:ok, res}
|
||||||
|
else
|
||||||
|
{:error, :origin_containment_failed} -> {:discard, :origin_containment_failed}
|
||||||
|
{:error, {:reject, reason}} -> {:discard, reason}
|
||||||
|
e -> e
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
11
mix.exs
11
mix.exs
|
@ -4,8 +4,8 @@ defmodule Pleroma.Mixfile do
|
||||||
def project do
|
def project do
|
||||||
[
|
[
|
||||||
app: :pleroma,
|
app: :pleroma,
|
||||||
version: version("3.0.1"),
|
version: version("3.1.0"),
|
||||||
elixir: "~> 1.9",
|
elixir: "~> 1.12",
|
||||||
elixirc_paths: elixirc_paths(Mix.env()),
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
|
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
|
||||||
elixirc_options: [warnings_as_errors: warnings_as_errors()],
|
elixirc_options: [warnings_as_errors: warnings_as_errors()],
|
||||||
|
@ -129,7 +129,7 @@ defp deps do
|
||||||
override: true},
|
override: true},
|
||||||
{:bcrypt_elixir, "~> 2.2"},
|
{:bcrypt_elixir, "~> 2.2"},
|
||||||
{:trailing_format_plug, "~> 0.0.7"},
|
{:trailing_format_plug, "~> 0.0.7"},
|
||||||
{:fast_sanitize, "~> 0.2.0"},
|
{:fast_sanitize, "~> 0.2.3"},
|
||||||
{:html_entities, "~> 0.5", override: true},
|
{:html_entities, "~> 0.5", override: true},
|
||||||
{:phoenix_html, "~> 3.1", override: true},
|
{:phoenix_html, "~> 3.1", override: true},
|
||||||
{:calendar, "~> 1.0"},
|
{:calendar, "~> 1.0"},
|
||||||
|
@ -191,6 +191,9 @@ defp deps do
|
||||||
{:ecto_psql_extras, "~> 0.6"},
|
{:ecto_psql_extras, "~> 0.6"},
|
||||||
{:elasticsearch,
|
{:elasticsearch,
|
||||||
git: "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", ref: "main"},
|
git: "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", ref: "main"},
|
||||||
|
{:mfm_parser,
|
||||||
|
git: "https://akkoma.dev/AkkomaGang/mfm-parser.git",
|
||||||
|
ref: "912fba81152d4d572e457fd5427f9875b2bc3dbe"},
|
||||||
|
|
||||||
# indirect dependency version override
|
# indirect dependency version override
|
||||||
{:plug, "~> 1.10.4", override: true},
|
{:plug, "~> 1.10.4", override: true},
|
||||||
|
@ -203,7 +206,7 @@ defp deps do
|
||||||
# temporary downgrade for excoveralls, hackney until hackney max_connections bug will be fixed
|
# temporary downgrade for excoveralls, hackney until hackney max_connections bug will be fixed
|
||||||
{:excoveralls, "0.12.3", only: :test},
|
{:excoveralls, "0.12.3", only: :test},
|
||||||
{:mox, "~> 1.0", only: :test},
|
{:mox, "~> 1.0", only: :test},
|
||||||
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test}
|
{:websockex, "~> 0.4.3", only: :test}
|
||||||
] ++ oauth_deps()
|
] ++ oauth_deps()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
4
mix.lock
4
mix.lock
|
@ -67,6 +67,7 @@
|
||||||
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
|
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
|
||||||
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
|
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
|
||||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
||||||
|
"mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "912fba81152d4d572e457fd5427f9875b2bc3dbe", [ref: "912fba81152d4d572e457fd5427f9875b2bc3dbe"]},
|
||||||
"mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
|
"mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
|
||||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
|
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
|
||||||
"mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"},
|
"mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"},
|
||||||
|
@ -109,6 +110,7 @@
|
||||||
"table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
|
"table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
|
||||||
"telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
|
"telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
|
||||||
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
|
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
|
||||||
|
"temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]},
|
||||||
"tesla": {:hex, :tesla, "1.4.4", "bb89aa0c9745190930366f6a2ac612cdf2d0e4d7fff449861baa7875afd797b2", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "d5503a49f9dec1b287567ea8712d085947e247cb11b06bc54adb05bfde466457"},
|
"tesla": {:hex, :tesla, "1.4.4", "bb89aa0c9745190930366f6a2ac612cdf2d0e4d7fff449861baa7875afd797b2", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "d5503a49f9dec1b287567ea8712d085947e247cb11b06bc54adb05bfde466457"},
|
||||||
"timex": {:hex, :timex, "3.7.8", "0e6e8bf7c0aba95f1e13204889b2446e7a5297b1c8e408f15ab58b2c8dc85f81", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8f3b8edc5faab5205d69e5255a1d64a83b190bab7f16baa78aefcb897cf81435"},
|
"timex": {:hex, :timex, "3.7.8", "0e6e8bf7c0aba95f1e13204889b2446e7a5297b1c8e408f15ab58b2c8dc85f81", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8f3b8edc5faab5205d69e5255a1d64a83b190bab7f16baa78aefcb897cf81435"},
|
||||||
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
||||||
|
@ -118,5 +120,5 @@
|
||||||
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
|
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
|
||||||
"vex": {:hex, :vex, "0.9.0", "613ea5eb3055662e7178b83e25b2df0975f68c3d8bb67c1645f0573e1a78d606", [:mix], [], "hexpm", "c69fff44d5c8aa3f1faee71bba1dcab05dd36364c5a629df8bb11751240c857f"},
|
"vex": {:hex, :vex, "0.9.0", "613ea5eb3055662e7178b83e25b2df0975f68c3d8bb67c1645f0573e1a78d606", [:mix], [], "hexpm", "c69fff44d5c8aa3f1faee71bba1dcab05dd36364c5a629df8bb11751240c857f"},
|
||||||
"web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"},
|
"web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"},
|
||||||
"websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []},
|
"websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
6396
priv/gettext/es/LC_MESSAGES/config_descriptions.po
Normal file
6396
priv/gettext/es/LC_MESSAGES/config_descriptions.po
Normal file
File diff suppressed because it is too large
Load diff
163
priv/gettext/es/LC_MESSAGES/posix_errors.po
Normal file
163
priv/gettext/es/LC_MESSAGES/posix_errors.po
Normal 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 ""
|
6396
priv/gettext/nl/LC_MESSAGES/config_descriptions.po
Normal file
6396
priv/gettext/nl/LC_MESSAGES/config_descriptions.po
Normal file
File diff suppressed because it is too large
Load diff
|
@ -3,16 +3,16 @@ msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-05-15 09:37+0000\n"
|
"POT-Creation-Date: 2020-05-15 09:37+0000\n"
|
||||||
"PO-Revision-Date: 2020-06-02 07:36+0000\n"
|
"PO-Revision-Date: 2022-08-07 10:46+0000\n"
|
||||||
"Last-Translator: Fristi <fristi@subcon.town>\n"
|
"Last-Translator: Fristi <fristi@subcon.town>\n"
|
||||||
"Language-Team: Dutch <https://translate.pleroma.social/projects/pleroma/"
|
"Language-Team: Dutch <http://translate.akkoma.dev/projects/akkoma/"
|
||||||
"pleroma/nl/>\n"
|
"akkoma-backend-errors/nl/>\n"
|
||||||
"Language: nl\n"
|
"Language: nl\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 4.0.4\n"
|
"X-Generator: Weblate 4.13.1\n"
|
||||||
|
|
||||||
## This file is a PO Template file.
|
## This file is a PO Template file.
|
||||||
##
|
##
|
||||||
|
@ -118,7 +118,7 @@ msgstr "Al gestemd"
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:360
|
#: lib/pleroma/web/oauth/oauth_controller.ex:360
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Bad request"
|
msgid "Bad request"
|
||||||
msgstr "Bad request"
|
msgstr "Ongeldig request"
|
||||||
|
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
|
@ -155,7 +155,7 @@ msgstr "Object kan niet geliked worden"
|
||||||
#: lib/pleroma/web/common_api/utils.ex:556
|
#: lib/pleroma/web/common_api/utils.ex:556
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Cannot post an empty status without attachments"
|
msgid "Cannot post an empty status without attachments"
|
||||||
msgstr "Status kan niet geplaatst worden zonder tekst of bijlagen"
|
msgstr "Bericht kan niet geplaatst worden zonder tekst of bijlagen"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/utils.ex:504
|
#: lib/pleroma/web/common_api/utils.ex:504
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
|
@ -165,122 +165,122 @@ msgstr "Opmerking dient maximaal %{max_size} karakters te bevatten"
|
||||||
#: lib/pleroma/config/config_db.ex:222
|
#: lib/pleroma/config/config_db.ex:222
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Config with params %{params} not found"
|
msgid "Config with params %{params} not found"
|
||||||
msgstr ""
|
msgstr "Instelling met parameters %{params} kon niet gevonden worden"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:95
|
#: lib/pleroma/web/common_api/common_api.ex:95
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Could not delete"
|
msgid "Could not delete"
|
||||||
msgstr ""
|
msgstr "Verwijderen mislukt"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:141
|
#: lib/pleroma/web/common_api/common_api.ex:141
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Could not favorite"
|
msgid "Could not favorite"
|
||||||
msgstr ""
|
msgstr "Favoriet maken mislukt"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:370
|
#: lib/pleroma/web/common_api/common_api.ex:370
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Could not pin"
|
msgid "Could not pin"
|
||||||
msgstr ""
|
msgstr "Vastmaken mislukt"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:112
|
#: lib/pleroma/web/common_api/common_api.ex:112
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Could not repeat"
|
msgid "Could not repeat"
|
||||||
msgstr ""
|
msgstr "Herhalen mislukt"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:188
|
#: lib/pleroma/web/common_api/common_api.ex:188
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Could not unfavorite"
|
msgid "Could not unfavorite"
|
||||||
msgstr ""
|
msgstr "Favoriet ongedaan maken mislukt"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:380
|
#: lib/pleroma/web/common_api/common_api.ex:380
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Could not unpin"
|
msgid "Could not unpin"
|
||||||
msgstr ""
|
msgstr "Vastmaken ongedaan maken mislukt"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:126
|
#: lib/pleroma/web/common_api/common_api.ex:126
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Could not unrepeat"
|
msgid "Could not unrepeat"
|
||||||
msgstr ""
|
msgstr "Herhalen ongedaan maken mislukt"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:428
|
#: lib/pleroma/web/common_api/common_api.ex:428
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:437
|
#: lib/pleroma/web/common_api/common_api.ex:437
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Could not update state"
|
msgid "Could not update state"
|
||||||
msgstr ""
|
msgstr "Status bijwerken mislukt"
|
||||||
|
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202
|
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Error."
|
msgid "Error."
|
||||||
msgstr ""
|
msgstr "Fout."
|
||||||
|
|
||||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:106
|
#: lib/pleroma/web/twitter_api/twitter_api.ex:106
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Invalid CAPTCHA"
|
msgid "Invalid CAPTCHA"
|
||||||
msgstr ""
|
msgstr "Ongeldige CAPTCHA"
|
||||||
|
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117
|
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:569
|
#: lib/pleroma/web/oauth/oauth_controller.ex:569
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Invalid credentials"
|
msgid "Invalid credentials"
|
||||||
msgstr ""
|
msgstr "Ongeldige inloggegevens"
|
||||||
|
|
||||||
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
|
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Invalid credentials."
|
msgid "Invalid credentials."
|
||||||
msgstr ""
|
msgstr "Ongeldige inloggegevens."
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:265
|
#: lib/pleroma/web/common_api/common_api.ex:265
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Invalid indices"
|
msgid "Invalid indices"
|
||||||
msgstr ""
|
msgstr "Ongeldige indexen"
|
||||||
|
|
||||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1147
|
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1147
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Invalid parameters"
|
msgid "Invalid parameters"
|
||||||
msgstr ""
|
msgstr "Ongeldige parameters"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/utils.ex:411
|
#: lib/pleroma/web/common_api/utils.ex:411
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Invalid password."
|
msgid "Invalid password."
|
||||||
msgstr ""
|
msgstr "Ongeldig wachtwoord."
|
||||||
|
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:187
|
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:187
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Invalid request"
|
msgid "Invalid request"
|
||||||
msgstr ""
|
msgstr "Ongeldig request"
|
||||||
|
|
||||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:109
|
#: lib/pleroma/web/twitter_api/twitter_api.ex:109
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Kocaptcha service unavailable"
|
msgid "Kocaptcha service unavailable"
|
||||||
msgstr ""
|
msgstr "Kocaptcha service niet beschikbaar"
|
||||||
|
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:113
|
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:113
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Missing parameters"
|
msgid "Missing parameters"
|
||||||
msgstr ""
|
msgstr "Ontbrekende parameters"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/utils.ex:540
|
#: lib/pleroma/web/common_api/utils.ex:540
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "No such conversation"
|
msgid "No such conversation"
|
||||||
msgstr ""
|
msgstr "Gesprek niet gevonden"
|
||||||
|
|
||||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:439
|
#: lib/pleroma/web/admin_api/admin_api_controller.ex:439
|
||||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:465 lib/pleroma/web/admin_api/admin_api_controller.ex:507
|
#: lib/pleroma/web/admin_api/admin_api_controller.ex:465 lib/pleroma/web/admin_api/admin_api_controller.ex:507
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "No such permission_group"
|
msgid "No such permission_group"
|
||||||
msgstr ""
|
msgstr "Permission_group niet gevonden"
|
||||||
|
|
||||||
#: lib/pleroma/plugs/uploaded_media.ex:74
|
#: lib/pleroma/plugs/uploaded_media.ex:74
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:485 lib/pleroma/web/admin_api/admin_api_controller.ex:1135
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:485 lib/pleroma/web/admin_api/admin_api_controller.ex:1135
|
||||||
#: lib/pleroma/web/feed/user_controller.ex:73 lib/pleroma/web/ostatus/ostatus_controller.ex:143
|
#: lib/pleroma/web/feed/user_controller.ex:73 lib/pleroma/web/ostatus/ostatus_controller.ex:143
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Not found"
|
msgid "Not found"
|
||||||
msgstr ""
|
msgstr "Niet gevonden"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:241
|
#: lib/pleroma/web/common_api/common_api.ex:241
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Poll's author can't vote"
|
msgid "Poll's author can't vote"
|
||||||
msgstr ""
|
msgstr "De peiling-auteur kan niet stemmen"
|
||||||
|
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
|
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
|
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
|
||||||
|
@ -288,215 +288,215 @@ msgstr ""
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
|
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Record not found"
|
msgid "Record not found"
|
||||||
msgstr ""
|
msgstr "Record niet gevonden"
|
||||||
|
|
||||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1153
|
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1153
|
||||||
#: lib/pleroma/web/feed/user_controller.ex:79 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:32
|
#: lib/pleroma/web/feed/user_controller.ex:79 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:32
|
||||||
#: lib/pleroma/web/ostatus/ostatus_controller.ex:149
|
#: lib/pleroma/web/ostatus/ostatus_controller.ex:149
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Something went wrong"
|
msgid "Something went wrong"
|
||||||
msgstr ""
|
msgstr "Er is iets misgegaan"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/activity_draft.ex:107
|
#: lib/pleroma/web/common_api/activity_draft.ex:107
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "The message visibility must be direct"
|
msgid "The message visibility must be direct"
|
||||||
msgstr ""
|
msgstr "De zichtbaarheid van het bericht dient privé te zijn"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/utils.ex:566
|
#: lib/pleroma/web/common_api/utils.ex:566
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "The status is over the character limit"
|
msgid "The status is over the character limit"
|
||||||
msgstr ""
|
msgstr "Het bericht is langer dan het karakter-limiet"
|
||||||
|
|
||||||
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
|
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "This resource requires authentication."
|
msgid "This resource requires authentication."
|
||||||
msgstr ""
|
msgstr "Deze gegevens vereisen authenticatie."
|
||||||
|
|
||||||
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
|
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Throttled"
|
msgid "Throttled"
|
||||||
msgstr ""
|
msgstr "Geremd"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:266
|
#: lib/pleroma/web/common_api/common_api.ex:266
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Too many choices"
|
msgid "Too many choices"
|
||||||
msgstr ""
|
msgstr "Teveel keuzes"
|
||||||
|
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Unhandled activity type"
|
msgid "Unhandled activity type"
|
||||||
msgstr ""
|
msgstr "Niet-ondersteund activiteits-type"
|
||||||
|
|
||||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:536
|
#: lib/pleroma/web/admin_api/admin_api_controller.ex:536
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "You can't revoke your own admin status."
|
msgid "You can't revoke your own admin status."
|
||||||
msgstr ""
|
msgstr "Je kan je eigen beheerdersrechten niet intrekken."
|
||||||
|
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:218
|
#: lib/pleroma/web/oauth/oauth_controller.ex:218
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:309
|
#: lib/pleroma/web/oauth/oauth_controller.ex:309
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Your account is currently disabled"
|
msgid "Your account is currently disabled"
|
||||||
msgstr ""
|
msgstr "Je account is momenteel uitgeschakeld"
|
||||||
|
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:180
|
#: lib/pleroma/web/oauth/oauth_controller.ex:180
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:332
|
#: lib/pleroma/web/oauth/oauth_controller.ex:332
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Your login is missing a confirmed e-mail address"
|
msgid "Your login is missing a confirmed e-mail address"
|
||||||
msgstr ""
|
msgstr "Je login bevat geen bevestigd e-mailadres"
|
||||||
|
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "can't read inbox of %{nickname} as %{as_nickname}"
|
msgid "can't read inbox of %{nickname} as %{as_nickname}"
|
||||||
msgstr ""
|
msgstr "kan de inbox van %{nickname} niet lezen als %{as_nickname}"
|
||||||
|
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "can't update outbox of %{nickname} as %{as_nickname}"
|
msgid "can't update outbox of %{nickname} as %{as_nickname}"
|
||||||
msgstr ""
|
msgstr "kan de outbox van %{nickname} niet bijwerken als %{as_nickname}"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:388
|
#: lib/pleroma/web/common_api/common_api.ex:388
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "conversation is already muted"
|
msgid "conversation is already muted"
|
||||||
msgstr ""
|
msgstr "gesprek is al genegeerd"
|
||||||
|
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "error"
|
msgid "error"
|
||||||
msgstr ""
|
msgstr "fout"
|
||||||
|
|
||||||
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:29
|
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:29
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "mascots can only be images"
|
msgid "mascots can only be images"
|
||||||
msgstr ""
|
msgstr "mascottes kunnen alleen afbeeldingen zijn"
|
||||||
|
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:60
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:60
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "not found"
|
msgid "not found"
|
||||||
msgstr ""
|
msgstr "niet gevonden"
|
||||||
|
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:395
|
#: lib/pleroma/web/oauth/oauth_controller.ex:395
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Bad OAuth request."
|
msgid "Bad OAuth request."
|
||||||
msgstr ""
|
msgstr "Ongeldig OAuth request."
|
||||||
|
|
||||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:115
|
#: lib/pleroma/web/twitter_api/twitter_api.ex:115
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "CAPTCHA already used"
|
msgid "CAPTCHA already used"
|
||||||
msgstr ""
|
msgstr "CAPTCHA is al gebruikt"
|
||||||
|
|
||||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:112
|
#: lib/pleroma/web/twitter_api/twitter_api.ex:112
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "CAPTCHA expired"
|
msgid "CAPTCHA expired"
|
||||||
msgstr ""
|
msgstr "CAPTCHA is verlopen"
|
||||||
|
|
||||||
#: lib/pleroma/plugs/uploaded_media.ex:55
|
#: lib/pleroma/plugs/uploaded_media.ex:55
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
msgstr ""
|
msgstr "Mislukt"
|
||||||
|
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:411
|
#: lib/pleroma/web/oauth/oauth_controller.ex:411
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Failed to authenticate: %{message}."
|
msgid "Failed to authenticate: %{message}."
|
||||||
msgstr ""
|
msgstr "Authenticatie mislukt: %{message}."
|
||||||
|
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:442
|
#: lib/pleroma/web/oauth/oauth_controller.ex:442
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Failed to set up user account."
|
msgid "Failed to set up user account."
|
||||||
msgstr ""
|
msgstr "Aanmaken van gebruikersaccount is mislukt."
|
||||||
|
|
||||||
#: lib/pleroma/plugs/oauth_scopes_plug.ex:38
|
#: lib/pleroma/plugs/oauth_scopes_plug.ex:38
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Insufficient permissions: %{permissions}."
|
msgid "Insufficient permissions: %{permissions}."
|
||||||
msgstr ""
|
msgstr "Niet voldoende rechten: %{permissions}."
|
||||||
|
|
||||||
#: lib/pleroma/plugs/uploaded_media.ex:94
|
#: lib/pleroma/plugs/uploaded_media.ex:94
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Internal Error"
|
msgid "Internal Error"
|
||||||
msgstr ""
|
msgstr "Interne Fout"
|
||||||
|
|
||||||
#: lib/pleroma/web/oauth/fallback_controller.ex:22
|
#: lib/pleroma/web/oauth/fallback_controller.ex:22
|
||||||
#: lib/pleroma/web/oauth/fallback_controller.ex:29
|
#: lib/pleroma/web/oauth/fallback_controller.ex:29
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Invalid Username/Password"
|
msgid "Invalid Username/Password"
|
||||||
msgstr ""
|
msgstr "Ongeldige Gebruikersnaam/Wachtwoord"
|
||||||
|
|
||||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:118
|
#: lib/pleroma/web/twitter_api/twitter_api.ex:118
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Invalid answer data"
|
msgid "Invalid answer data"
|
||||||
msgstr ""
|
msgstr "Ongeldig antwoord"
|
||||||
|
|
||||||
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128
|
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Nodeinfo schema version not handled"
|
msgid "Nodeinfo schema version not handled"
|
||||||
msgstr ""
|
msgstr "Nodeinfo schema wordt niet ondersteund"
|
||||||
|
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:169
|
#: lib/pleroma/web/oauth/oauth_controller.ex:169
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "This action is outside the authorized scopes"
|
msgid "This action is outside the authorized scopes"
|
||||||
msgstr ""
|
msgstr "Deze actie bevindt zich buiten de gemachtigde scopes"
|
||||||
|
|
||||||
#: lib/pleroma/web/oauth/fallback_controller.ex:14
|
#: lib/pleroma/web/oauth/fallback_controller.ex:14
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Unknown error, please check the details and try again."
|
msgid "Unknown error, please check the details and try again."
|
||||||
msgstr ""
|
msgstr "Onbekende fout, controleer a.u.b. de details en probeer het opnieuw."
|
||||||
|
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:116
|
#: lib/pleroma/web/oauth/oauth_controller.ex:116
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:155
|
#: lib/pleroma/web/oauth/oauth_controller.ex:155
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Unlisted redirect_uri."
|
msgid "Unlisted redirect_uri."
|
||||||
msgstr ""
|
msgstr "Niet-vermelde redirect_uri."
|
||||||
|
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:391
|
#: lib/pleroma/web/oauth/oauth_controller.ex:391
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Unsupported OAuth provider: %{provider}."
|
msgid "Unsupported OAuth provider: %{provider}."
|
||||||
msgstr ""
|
msgstr "Niet ondersteunde OAuth provider: %{provider}."
|
||||||
|
|
||||||
#: lib/pleroma/uploaders/uploader.ex:72
|
#: lib/pleroma/uploaders/uploader.ex:72
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Uploader callback timeout"
|
msgid "Uploader callback timeout"
|
||||||
msgstr ""
|
msgstr "Uploader terugkoppeling timeout"
|
||||||
|
|
||||||
#: lib/pleroma/web/uploader_controller.ex:23
|
#: lib/pleroma/web/uploader_controller.ex:23
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "bad request"
|
msgid "bad request"
|
||||||
msgstr ""
|
msgstr "ongeldig request"
|
||||||
|
|
||||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:103
|
#: lib/pleroma/web/twitter_api/twitter_api.ex:103
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "CAPTCHA Error"
|
msgid "CAPTCHA Error"
|
||||||
msgstr ""
|
msgstr "CAPTCHA Fout"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:200
|
#: lib/pleroma/web/common_api/common_api.ex:200
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Could not add reaction emoji"
|
msgid "Could not add reaction emoji"
|
||||||
msgstr ""
|
msgstr "Reactie-emoji toevoegen mislukt"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:211
|
#: lib/pleroma/web/common_api/common_api.ex:211
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Could not remove reaction emoji"
|
msgid "Could not remove reaction emoji"
|
||||||
msgstr ""
|
msgstr "Reactie-emoji verwijderen mislukt"
|
||||||
|
|
||||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:129
|
#: lib/pleroma/web/twitter_api/twitter_api.ex:129
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Invalid CAPTCHA (Missing parameter: %{name})"
|
msgid "Invalid CAPTCHA (Missing parameter: %{name})"
|
||||||
msgstr ""
|
msgstr "Ongeldige CAPTCHA (Ontbrekende parameter: %{name})"
|
||||||
|
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
|
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "List not found"
|
msgid "List not found"
|
||||||
msgstr ""
|
msgstr "Lijst niet gevonden"
|
||||||
|
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:124
|
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:124
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Missing parameter: %{name}"
|
msgid "Missing parameter: %{name}"
|
||||||
msgstr ""
|
msgstr "Ontbrekende parameter: %{name}"
|
||||||
|
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:207
|
#: lib/pleroma/web/oauth/oauth_controller.ex:207
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:322
|
#: lib/pleroma/web/oauth/oauth_controller.ex:322
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Password reset is required"
|
msgid "Password reset is required"
|
||||||
msgstr ""
|
msgstr "Wachtwoordherstel is vereist"
|
||||||
|
|
||||||
#: lib/pleroma/tests/auth_test_controller.ex:9
|
#: lib/pleroma/tests/auth_test_controller.ex:9
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/admin_api_controller.ex:6
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/admin_api_controller.ex:6
|
||||||
|
@ -528,53 +528,63 @@ msgstr ""
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
|
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Schending van beveiliging: OAuth scope-controle is niet uitgevoerd en niet "
|
||||||
|
"expliciet overgeslagen."
|
||||||
|
|
||||||
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
|
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Two-factor authentication enabled, you must use a access token."
|
msgid "Two-factor authentication enabled, you must use a access token."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Tweefactor authenticatie is ingeschakeld, een toegangssleutel is verplicht."
|
||||||
|
|
||||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210
|
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Unexpected error occurred while adding file to pack."
|
msgid "Unexpected error occurred while adding file to pack."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Er is een onverwachte fout opgetreden tijdens het toevoegen van het bestand."
|
||||||
|
|
||||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138
|
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Unexpected error occurred while creating pack."
|
msgid "Unexpected error occurred while creating pack."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Er is een onverwachte fout opgetreden tijdens het aanmaken van het pakket."
|
||||||
|
|
||||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278
|
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Unexpected error occurred while removing file from pack."
|
msgid "Unexpected error occurred while removing file from pack."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Er is een onverwachte fout opgetreden tijdens het verwijderen van het "
|
||||||
|
"bestand."
|
||||||
|
|
||||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250
|
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Unexpected error occurred while updating file in pack."
|
msgid "Unexpected error occurred while updating file in pack."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Er is een onverwachte fout opgetreden tijdens het bijwerken van het bestand."
|
||||||
|
|
||||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179
|
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Unexpected error occurred while updating pack metadata."
|
msgid "Unexpected error occurred while updating pack metadata."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Er is een onverwachte fout opgetreden tijdens het bijwerken van de pakket-"
|
||||||
|
"metadata."
|
||||||
|
|
||||||
#: lib/pleroma/plugs/user_is_admin_plug.ex:21
|
#: lib/pleroma/plugs/user_is_admin_plug.ex:21
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "User is not an admin."
|
msgid "User is not an admin."
|
||||||
msgstr ""
|
msgstr "Gebruiker is niet een beheerder."
|
||||||
|
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
|
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Web push subscription is disabled on this Pleroma instance"
|
msgid "Web push subscription is disabled on this Pleroma instance"
|
||||||
msgstr ""
|
msgstr "Web push abbonement is uitgeschakeld op deze Pleroma instantie"
|
||||||
|
|
||||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:502
|
#: lib/pleroma/web/admin_api/admin_api_controller.ex:502
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "You can't revoke your own admin/moderator status."
|
msgid "You can't revoke your own admin/moderator status."
|
||||||
msgstr ""
|
msgstr "Je kan je eigen beheerders- of moderatorrechten niet intrekken."
|
||||||
|
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:105
|
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:105
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "authorization required for timeline view"
|
msgid "authorization required for timeline view"
|
||||||
msgstr ""
|
msgstr "machtiging is vereist voor de tijdlijn weergave"
|
||||||
|
|
567
priv/gettext/nl/LC_MESSAGES/static_pages.po
Normal file
567
priv/gettext/nl/LC_MESSAGES/static_pages.po
Normal 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"
|
|
@ -56,8 +56,36 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
||||||
Meta.allow_tag_with_these_attributes(:u, [])
|
Meta.allow_tag_with_these_attributes(:u, [])
|
||||||
Meta.allow_tag_with_these_attributes(:ul, [])
|
Meta.allow_tag_with_these_attributes(:ul, [])
|
||||||
|
|
||||||
Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card", "quote-inline"])
|
Meta.allow_tags_with_style_attributes([:span])
|
||||||
Meta.allow_tag_with_these_attributes(:span, [])
|
|
||||||
|
Meta.allow_tag_with_this_attribute_values(:span, "class", [
|
||||||
|
"h-card",
|
||||||
|
"quote-inline",
|
||||||
|
"mfm",
|
||||||
|
"mfm _mfm_tada_",
|
||||||
|
"mfm _mfm_jelly_",
|
||||||
|
"mfm _mfm_twitch_",
|
||||||
|
"mfm _mfm_shake_",
|
||||||
|
"mfm _mfm_spin_",
|
||||||
|
"mfm _mfm_jump_",
|
||||||
|
"mfm _mfm_bounce_",
|
||||||
|
"mfm _mfm_flip_",
|
||||||
|
"mfm _mfm_x2_",
|
||||||
|
"mfm _mfm_x3_",
|
||||||
|
"mfm _mfm_x4_",
|
||||||
|
"mfm _mfm_blur_",
|
||||||
|
"mfm _mfm_rainbow_",
|
||||||
|
"mfm _mfm_rotate_"
|
||||||
|
])
|
||||||
|
|
||||||
|
Meta.allow_tag_with_these_attributes(:span, [
|
||||||
|
"data-x",
|
||||||
|
"data-y",
|
||||||
|
"data-h",
|
||||||
|
"data-v",
|
||||||
|
"data-left",
|
||||||
|
"data-right"
|
||||||
|
])
|
||||||
|
|
||||||
Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"])
|
Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"])
|
||||||
|
|
||||||
|
@ -101,4 +129,6 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
||||||
Meta.allow_tag_with_these_attributes(:small, [])
|
Meta.allow_tag_with_these_attributes(:small, [])
|
||||||
|
|
||||||
Meta.strip_everything_not_covered()
|
Meta.strip_everything_not_covered()
|
||||||
|
|
||||||
|
defp scrub_css(value), do: value
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
# XXX: This should be removed when elixir's releases get custom command support
|
# XXX: This should be removed when elixir's releases get custom command support
|
||||||
|
|
||||||
detect_flavour() {
|
detect_flavour() {
|
||||||
echo "Trying to autodetect flavour, you may want to override this with --flavour"
|
|
||||||
arch="$(uname -m)"
|
arch="$(uname -m)"
|
||||||
if [ "$arch" = "x86_64" ]; then
|
if [ "$arch" = "x86_64" ]; then
|
||||||
arch="amd64"
|
arch="amd64"
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Pleroma social network
|
Description=Akkoma social network
|
||||||
After=network.target postgresql.service nginx.service
|
After=network.target postgresql.service nginx.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
KillMode=process
|
KillMode=process
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|
||||||
; Name of the user that runs the Pleroma service.
|
; Name of the user that runs the Akkoma service.
|
||||||
User=pleroma
|
User=akkoma
|
||||||
|
|
||||||
; Make sure that all paths fit your installation.
|
; Make sure that all paths fit your installation.
|
||||||
; Path to the home directory of the user running the Pleroma service.
|
; Path to the home directory of the user running the Akkoma service.
|
||||||
Environment="HOME=/opt/pleroma"
|
Environment="HOME=/opt/akkoma"
|
||||||
; Path to the folder containing the Pleroma installation.
|
; Path to the folder containing the Akkoma installation.
|
||||||
WorkingDirectory=/opt/pleroma
|
WorkingDirectory=/opt/akkoma
|
||||||
; Path to the Pleroma binary.
|
; Path to the Mix binary.
|
||||||
ExecStart=/opt/pleroma/bin/pleroma start
|
ExecStart=/opt/akkoma/bin/pleroma start
|
||||||
ExecStop=/opt/pleroma/bin/pleroma stop
|
ExecStop=/opt/akkoma/bin/pleroma stop
|
||||||
|
|
||||||
; Some security directives.
|
; Some security directives.
|
||||||
; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops.
|
; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops.
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
; The /home, /root, and /run/user folders can not be accessed by this service anymore. If your Pleroma user has its home folder in one of the restricted places, or use one of these folders as its working directory, you have to set this to false.
|
; The /home, /root, and /run/user folders can not be accessed by this service anymore. If your Akkoma user has its home folder in one of the restricted places, or use one of these folders as its working directory, you have to set this to false.
|
||||||
ProtectHome=true
|
ProtectHome=true
|
||||||
; Mount /usr, /boot, and /etc as read-only for processes invoked by this service.
|
; Mount /usr, /boot, and /etc as read-only for processes invoked by this service.
|
||||||
ProtectSystem=full
|
ProtectSystem=full
|
|
@ -3,17 +3,17 @@
|
||||||
supervisor=supervise-daemon
|
supervisor=supervise-daemon
|
||||||
|
|
||||||
# Requires OpenRC >= 0.35
|
# Requires OpenRC >= 0.35
|
||||||
directory=/opt/pleroma
|
directory=/opt/akkoma
|
||||||
|
|
||||||
command=/opt/pleroma/bin/pleroma
|
command=/opt/akkoma/bin/pleroma
|
||||||
command_args="start"
|
command_args="start"
|
||||||
command_user=pleroma
|
command_user=akkoma
|
||||||
command_background=1
|
command_background=1
|
||||||
|
|
||||||
# Ask process to terminate within 30 seconds, otherwise kill it
|
# Ask process to terminate within 30 seconds, otherwise kill it
|
||||||
retry="SIGTERM/30/SIGKILL/5"
|
retry="SIGTERM/30/SIGKILL/5"
|
||||||
|
|
||||||
pidfile="/var/run/pleroma.pid"
|
pidfile="/var/run/akkoma.pid"
|
||||||
|
|
||||||
depend() {
|
depend() {
|
||||||
want nginx
|
want nginx
|
|
@ -1 +1,5 @@
|
||||||
external_emoji, https://example.com/emoji.png
|
external_emoji, https://example.com/emoji.png
|
||||||
|
firefox, /emoji/Firefox.gif, Gif,Fun
|
||||||
|
blank, /emoji/blank.png, Fun
|
||||||
|
dinosaur, /emoji/dino walking.gif, Gif
|
||||||
|
100a, /emoji/100a.png, Fun
|
||||||
|
|
2
test/fixtures/misskey/mfm_x_format.json
vendored
2
test/fixtures/misskey/mfm_x_format.json
vendored
|
@ -3,7 +3,7 @@
|
||||||
"type": "Note",
|
"type": "Note",
|
||||||
"attributedTo": "https://misskey.local.live/users/92hzkskwgy",
|
"attributedTo": "https://misskey.local.live/users/92hzkskwgy",
|
||||||
"summary": null,
|
"summary": null,
|
||||||
"content": "this gets replaced",
|
"content": "this does not get replaced",
|
||||||
"source": {
|
"source": {
|
||||||
"content": "@akkoma_user @remote_user @full_tag_remote_user@misskey.local.live @oops_not_a_mention linkifylink #dancedance $[jelly mfm goes here] \n\n## aaa",
|
"content": "@akkoma_user @remote_user @full_tag_remote_user@misskey.local.live @oops_not_a_mention linkifylink #dancedance $[jelly mfm goes here] \n\n## aaa",
|
||||||
"mediaType": "text/x.misskeymarkdown"
|
"mediaType": "text/x.misskeymarkdown"
|
||||||
|
|
|
@ -30,7 +30,7 @@ test "it should extract items from an embedded array in a Collection" do
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
{:ok, objects} = Fetcher.fetch_collection_by_ap_id(ap_id)
|
{:ok, objects} = Fetcher.fetch_collection(ap_id)
|
||||||
assert [%{"type" => "Create"}, %{"type" => "Like"}] = objects
|
assert [%{"type" => "Create"}, %{"type" => "Like"}] = objects
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ test "it should extract items from an embedded array in an OrderedCollection" do
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
{:ok, objects} = Fetcher.fetch_collection_by_ap_id(ap_id)
|
{:ok, objects} = Fetcher.fetch_collection(ap_id)
|
||||||
assert [%{"type" => "Create"}, %{"type" => "Like"}] = objects
|
assert [%{"type" => "Create"}, %{"type" => "Like"}] = objects
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ test "it should extract items from an referenced first page in a Collection" do
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
{:ok, objects} = Fetcher.fetch_collection_by_ap_id(ap_id)
|
{:ok, objects} = Fetcher.fetch_collection(ap_id)
|
||||||
assert [%{"type" => "Create"}, %{"type" => "Like"}] = objects
|
assert [%{"type" => "Create"}, %{"type" => "Like"}] = objects
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -161,7 +161,58 @@ test "it should stop fetching when we hit :max_collection_objects" do
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
{:ok, objects} = Fetcher.fetch_collection_by_ap_id(ap_id)
|
{:ok, objects} = Fetcher.fetch_collection(ap_id)
|
||||||
|
assert [%{"type" => "Create"}] = objects
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it should stop fetching when we hit a 404" do
|
||||||
|
clear_config([:activitypub, :max_collection_objects], 1)
|
||||||
|
|
||||||
|
unordered_collection =
|
||||||
|
"test/fixtures/collections/unordered_page_reference.json"
|
||||||
|
|> File.read!()
|
||||||
|
|
||||||
|
first_page =
|
||||||
|
"test/fixtures/collections/unordered_page_first.json"
|
||||||
|
|> File.read!()
|
||||||
|
|
||||||
|
ap_id = "https://example.com/collection/unordered_page_reference"
|
||||||
|
first_page_id = "https://example.com/collection/unordered_page_reference?page=1"
|
||||||
|
second_page_id = "https://example.com/collection/unordered_page_reference?page=2"
|
||||||
|
|
||||||
|
Tesla.Mock.mock(fn
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: ^ap_id
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: unordered_collection,
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: ^first_page_id
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: first_page,
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: ^second_page_id
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 404,
|
||||||
|
body: nil,
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, objects} = Fetcher.fetch_collection(ap_id)
|
||||||
assert [%{"type" => "Create"}] = objects
|
assert [%{"type" => "Create"}] = objects
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -13,16 +13,38 @@ test "with nil" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "with string" do
|
test "with string" do
|
||||||
assert AdapterHelper.format_proxy("127.0.0.1:8123") == {{127, 0, 0, 1}, 8123}
|
assert AdapterHelper.format_proxy("http://127.0.0.1:8123") == {:http, "127.0.0.1", 8123, []}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "localhost with port" do
|
test "localhost with port" do
|
||||||
assert AdapterHelper.format_proxy("localhost:8123") == {'localhost', 8123}
|
assert AdapterHelper.format_proxy("https://localhost:8123") ==
|
||||||
|
{:https, "localhost", 8123, []}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "tuple" do
|
test "tuple" do
|
||||||
assert AdapterHelper.format_proxy({:socks4, :localhost, 9050}) ==
|
assert AdapterHelper.format_proxy({:http, "localhost", 9050}) ==
|
||||||
{:socks4, 'localhost', 9050}
|
{:http, "localhost", 9050, []}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "maybe_add_proxy_pool/1" do
|
||||||
|
test "should do nothing with nil" do
|
||||||
|
assert AdapterHelper.maybe_add_proxy_pool([], nil) == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should create pools" do
|
||||||
|
assert AdapterHelper.maybe_add_proxy_pool([], "proxy") == [
|
||||||
|
pools: %{default: [conn_opts: [proxy: "proxy"]]}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should not override conn_opts if set" do
|
||||||
|
assert AdapterHelper.maybe_add_proxy_pool(
|
||||||
|
[pools: %{default: [conn_opts: [already: "set"]]}],
|
||||||
|
"proxy"
|
||||||
|
) == [
|
||||||
|
pools: %{default: [conn_opts: [proxy: "proxy", already: "set"]]}
|
||||||
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,18 +31,23 @@ def start_socket(qs \\ nil, headers \\ []) do
|
||||||
WebsocketClient.start_link(self(), path, headers)
|
WebsocketClient.start_link(self(), path, headers)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "refuses invalid requests" do
|
test "allows multi-streams" do
|
||||||
capture_log(fn ->
|
capture_log(fn ->
|
||||||
assert {:error, {404, _}} = start_socket()
|
assert {:ok, _} = start_socket()
|
||||||
assert {:error, {404, _}} = start_socket("?stream=ncjdk")
|
|
||||||
|
assert {:error, %WebSockex.RequestError{code: 404, message: "Not Found"}} =
|
||||||
|
start_socket("?stream=ncjdk")
|
||||||
|
|
||||||
Process.sleep(30)
|
Process.sleep(30)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "requires authentication and a valid token for protected streams" do
|
test "requires authentication and a valid token for protected streams" do
|
||||||
capture_log(fn ->
|
capture_log(fn ->
|
||||||
assert {:error, {401, _}} = start_socket("?stream=user&access_token=aaaaaaaaaaaa")
|
assert {:error, %WebSockex.RequestError{code: 401}} =
|
||||||
assert {:error, {401, _}} = start_socket("?stream=user")
|
start_socket("?stream=user&access_token=aaaaaaaaaaaa")
|
||||||
|
|
||||||
|
assert {:error, %WebSockex.RequestError{code: 401}} = start_socket("?stream=user")
|
||||||
Process.sleep(30)
|
Process.sleep(30)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -91,7 +96,7 @@ test "receives well formatted events" do
|
||||||
|
|
||||||
{:ok, token} = OAuth.Token.exchange_token(app, auth)
|
{:ok, token} = OAuth.Token.exchange_token(app, auth)
|
||||||
|
|
||||||
%{user: user, token: token}
|
%{app: app, user: user, token: token}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "accepts valid tokens", state do
|
test "accepts valid tokens", state do
|
||||||
|
@ -102,7 +107,7 @@ test "accepts the 'user' stream", %{token: token} = _state do
|
||||||
assert {:ok, _} = start_socket("?stream=user&access_token=#{token.token}")
|
assert {:ok, _} = start_socket("?stream=user&access_token=#{token.token}")
|
||||||
|
|
||||||
capture_log(fn ->
|
capture_log(fn ->
|
||||||
assert {:error, {401, _}} = start_socket("?stream=user")
|
assert {:error, %WebSockex.RequestError{code: 401}} = start_socket("?stream=user")
|
||||||
Process.sleep(30)
|
Process.sleep(30)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -111,7 +116,9 @@ test "accepts the 'user:notification' stream", %{token: token} = _state do
|
||||||
assert {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}")
|
assert {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}")
|
||||||
|
|
||||||
capture_log(fn ->
|
capture_log(fn ->
|
||||||
assert {:error, {401, _}} = start_socket("?stream=user:notification")
|
assert {:error, %WebSockex.RequestError{code: 401}} =
|
||||||
|
start_socket("?stream=user:notification")
|
||||||
|
|
||||||
Process.sleep(30)
|
Process.sleep(30)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -120,11 +127,27 @@ test "accepts valid token on Sec-WebSocket-Protocol header", %{token: token} do
|
||||||
assert {:ok, _} = start_socket("?stream=user", [{"Sec-WebSocket-Protocol", token.token}])
|
assert {:ok, _} = start_socket("?stream=user", [{"Sec-WebSocket-Protocol", token.token}])
|
||||||
|
|
||||||
capture_log(fn ->
|
capture_log(fn ->
|
||||||
assert {:error, {401, _}} =
|
assert {:error, %WebSockex.RequestError{code: 401}} =
|
||||||
start_socket("?stream=user", [{"Sec-WebSocket-Protocol", "I am a friend"}])
|
start_socket("?stream=user", [{"Sec-WebSocket-Protocol", "I am a friend"}])
|
||||||
|
|
||||||
Process.sleep(30)
|
Process.sleep(30)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "disconnect when token is revoked", %{app: app, user: user, token: token} do
|
||||||
|
assert {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}")
|
||||||
|
assert {:ok, _} = start_socket("?stream=user&access_token=#{token.token}")
|
||||||
|
|
||||||
|
{:ok, auth} = OAuth.Authorization.create_authorization(app, user)
|
||||||
|
|
||||||
|
{:ok, token2} = OAuth.Token.exchange_token(app, auth)
|
||||||
|
assert {:ok, _} = start_socket("?stream=user&access_token=#{token2.token}")
|
||||||
|
|
||||||
|
OAuth.Token.Strategy.Revoke.revoke(token)
|
||||||
|
|
||||||
|
assert_receive {:close, _}
|
||||||
|
assert_receive {:close, _}
|
||||||
|
refute_receive {:close, _}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -224,7 +224,7 @@ test "it creates a notification for user and send to the 'user' and the 'user:no
|
||||||
task =
|
task =
|
||||||
Task.async(fn ->
|
Task.async(fn ->
|
||||||
{:ok, _topic} = Streamer.get_topic_and_add_socket("user", user, oauth_token)
|
{:ok, _topic} = Streamer.get_topic_and_add_socket("user", user, oauth_token)
|
||||||
assert_receive {:render_with_user, _, _, _}, 4_000
|
assert_receive {:render_with_user, _, _, _, "user"}, 4_000
|
||||||
end)
|
end)
|
||||||
|
|
||||||
task_user_notification =
|
task_user_notification =
|
||||||
|
@ -232,7 +232,7 @@ test "it creates a notification for user and send to the 'user' and the 'user:no
|
||||||
{:ok, _topic} =
|
{:ok, _topic} =
|
||||||
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
|
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
|
||||||
|
|
||||||
assert_receive {:render_with_user, _, _, _}, 4_000
|
assert_receive {:render_with_user, _, _, _, "user:notification"}, 4_000
|
||||||
end)
|
end)
|
||||||
|
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
|
|
|
@ -620,13 +620,14 @@ test "it blocks blacklisted email domains" do
|
||||||
assert changeset.valid?
|
assert changeset.valid?
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it sets the password_hash and ap_id" do
|
test "it sets the password_hash, ap_id and PEM key" do
|
||||||
changeset = User.register_changeset(%User{}, @full_user_data)
|
changeset = User.register_changeset(%User{}, @full_user_data)
|
||||||
|
|
||||||
assert changeset.valid?
|
assert changeset.valid?
|
||||||
|
|
||||||
assert is_binary(changeset.changes[:password_hash])
|
assert is_binary(changeset.changes[:password_hash])
|
||||||
assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname})
|
assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname})
|
||||||
|
assert is_binary(changeset.changes[:keys])
|
||||||
|
|
||||||
assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
|
assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
|
||||||
end
|
end
|
||||||
|
|
|
@ -782,6 +782,7 @@ test "mastodon pin/unpin", %{conn: conn} do
|
||||||
|> String.replace("{{status_id}}", status_id)
|
|> String.replace("{{status_id}}", status_id)
|
||||||
|
|
||||||
status_url = "https://example.com/users/lain/statuses/#{status_id}"
|
status_url = "https://example.com/users/lain/statuses/#{status_id}"
|
||||||
|
replies_url = status_url <> "/replies?only_other_accounts=true&page=true"
|
||||||
|
|
||||||
user =
|
user =
|
||||||
File.read!("test/fixtures/users_mock/user.json")
|
File.read!("test/fixtures/users_mock/user.json")
|
||||||
|
@ -820,6 +821,16 @@ test "mastodon pin/unpin", %{conn: conn} do
|
||||||
|> String.replace("{{nickname}}", "lain"),
|
|> String.replace("{{nickname}}", "lain"),
|
||||||
headers: [{"content-type", "application/activity+json"}]
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: ^replies_url
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 404,
|
||||||
|
body: "",
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
|
|
|
@ -216,6 +216,43 @@ test "has a matching host but only as:Public in to" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "describe/1" do
|
||||||
|
test "returns a description of the policy" do
|
||||||
|
clear_config([:mrf_simple, :reject], [
|
||||||
|
{"remote.instance", "did not give my catboy a burg"}
|
||||||
|
])
|
||||||
|
|
||||||
|
assert {:ok, %{mrf_simple: %{reject: ["remote.instance"]}}} = SimplePolicy.describe()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "excludes domains listed in :transparency_exclusions" do
|
||||||
|
clear_config([:mrf, :transparency_exclusions], [{"remote.instance", ":("}])
|
||||||
|
|
||||||
|
clear_config([:mrf_simple, :reject], [
|
||||||
|
{"remote.instance", "did not give my catboy a burg"}
|
||||||
|
])
|
||||||
|
|
||||||
|
{:ok, description} = SimplePolicy.describe()
|
||||||
|
assert %{mrf_simple: %{reject: []}} = description
|
||||||
|
assert description[:mrf_simple_info][:reject] == nil
|
||||||
|
end
|
||||||
|
|
||||||
|
test "obfuscates domains listed in :transparency_obfuscate_domains" do
|
||||||
|
clear_config([:mrf, :transparency_obfuscate_domains], ["remote.instance", "a.b"])
|
||||||
|
|
||||||
|
clear_config([:mrf_simple, :reject], [
|
||||||
|
{"remote.instance", "did not give my catboy a burg"},
|
||||||
|
{"a.b", "spam-poked me on facebook in 2006"}
|
||||||
|
])
|
||||||
|
|
||||||
|
assert {:ok,
|
||||||
|
%{
|
||||||
|
mrf_simple: %{reject: ["rem***.*****nce", "a.b"]},
|
||||||
|
mrf_simple_info: %{reject: %{"rem***.*****nce" => %{}}}
|
||||||
|
}} = SimplePolicy.describe()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp build_ftl_actor_and_message do
|
defp build_ftl_actor_and_message do
|
||||||
actor = insert(:user)
|
actor = insert(:user)
|
||||||
|
|
||||||
|
|
|
@ -98,8 +98,6 @@ test "a misskey MFM status with a content field should work and be linked", _ do
|
||||||
changes: %{
|
changes: %{
|
||||||
content: content,
|
content: content,
|
||||||
source: %{
|
source: %{
|
||||||
"content" =>
|
|
||||||
"@akkoma_user @remote_user @full_tag_remote_user@misskey.local.live @oops_not_a_mention linkifylink #dancedance $[jelly mfm goes here] \n\n## aaa",
|
|
||||||
"mediaType" => "text/x.misskeymarkdown"
|
"mediaType" => "text/x.misskeymarkdown"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,7 +113,9 @@ test "a misskey MFM status with a content field should work and be linked", _ do
|
||||||
"<span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{full_tag_remote_user.id}\" href=\"#{full_tag_remote_user.ap_id}\" rel=\"ugc\">@<span>full_tag_remote_user</span></a></span>"
|
"<span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{full_tag_remote_user.id}\" href=\"#{full_tag_remote_user.ap_id}\" rel=\"ugc\">@<span>full_tag_remote_user</span></a></span>"
|
||||||
|
|
||||||
assert content =~ "@oops_not_a_mention"
|
assert content =~ "@oops_not_a_mention"
|
||||||
assert content =~ "$[jelly mfm goes here] <br><br>## aaa"
|
|
||||||
|
assert content =~
|
||||||
|
"<span class=\"mfm _mfm_jelly_\" style=\"display: inline-block; animation: 1s linear 0s infinite normal both running mfm-rubberBand;\">mfm goes here</span> </p>aaa"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "a misskey MFM status with a _misskey_content field should work and be linked", _ do
|
test "a misskey MFM status with a _misskey_content field should work and be linked", _ do
|
||||||
|
@ -129,22 +129,34 @@ test "a misskey MFM status with a _misskey_content field should work and be link
|
||||||
|> File.read!()
|
|> File.read!()
|
||||||
|> Jason.decode!()
|
|> Jason.decode!()
|
||||||
|
|
||||||
expected_content =
|
|
||||||
"<span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{local_user.id}\" href=\"#{local_user.ap_id}\" rel=\"ugc\">@<span>akkoma_user</span></a></span> linkifylink <a class=\"hashtag\" data-tag=\"dancedance\" href=\"http://localhost:4001/tag/dancedance\">#dancedance</a> $[jelly mfm goes here] <br><br>## aaa"
|
|
||||||
|
|
||||||
changes = ArticleNotePageValidator.cast_and_validate(note)
|
changes = ArticleNotePageValidator.cast_and_validate(note)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
valid?: true,
|
valid?: true,
|
||||||
changes: %{
|
changes: %{
|
||||||
|
content: content,
|
||||||
source: %{
|
source: %{
|
||||||
"content" => "@akkoma_user linkifylink #dancedance $[jelly mfm goes here] \n\n## aaa",
|
"mediaType" => "text/x.misskeymarkdown",
|
||||||
"mediaType" => "text/x.misskeymarkdown"
|
"content" => "@akkoma_user linkifylink #dancedance $[jelly mfm goes here] \n\n## aaa"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} = changes
|
} = changes
|
||||||
|
|
||||||
assert changes.changes[:content] == expected_content
|
assert content =~
|
||||||
|
"<span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{local_user.id}\" href=\"#{local_user.ap_id}\" rel=\"ugc\">@<span>akkoma_user</span></a></span>"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "a Note without replies/first/items validates" do
|
||||||
|
insert(:user, ap_id: "https://mastodon.social/users/emelie")
|
||||||
|
|
||||||
|
note =
|
||||||
|
"test/fixtures/tesla_mock/status.emelie.json"
|
||||||
|
|> File.read!()
|
||||||
|
|> Jason.decode!()
|
||||||
|
|> pop_in(["replies", "first", "items"])
|
||||||
|
|> elem(1)
|
||||||
|
|
||||||
|
%{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -380,7 +380,6 @@ test "schedules background fetching of `replies` items if max thread depth limit
|
||||||
clear_config([:instance, :federation_incoming_replies_max_depth], 10)
|
clear_config([:instance, :federation_incoming_replies_max_depth], 10)
|
||||||
|
|
||||||
{:ok, activity} = Transmogrifier.handle_incoming(data)
|
{:ok, activity} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
object = Object.normalize(activity.data["object"])
|
object = Object.normalize(activity.data["object"])
|
||||||
|
|
||||||
assert object.data["replies"] == items
|
assert object.data["replies"] == items
|
||||||
|
|
|
@ -153,7 +153,7 @@ test "rejects incoming AP docs with incorrect origin" do
|
||||||
}
|
}
|
||||||
|
|
||||||
assert {:ok, job} = Federator.incoming_ap_doc(params)
|
assert {:ok, job} = Federator.incoming_ap_doc(params)
|
||||||
assert {:error, :origin_containment_failed} = ObanHelpers.perform(job)
|
assert {:discard, :origin_containment_failed} = ObanHelpers.perform(job)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not crash if MRF rejects the post" do
|
test "it does not crash if MRF rejects the post" do
|
||||||
|
@ -169,7 +169,7 @@ test "it does not crash if MRF rejects the post" do
|
||||||
|> Jason.decode!()
|
|> Jason.decode!()
|
||||||
|
|
||||||
assert {:ok, job} = Federator.incoming_ap_doc(params)
|
assert {:ok, job} = Federator.incoming_ap_doc(params)
|
||||||
assert {:error, _} = ObanHelpers.perform(job)
|
assert {:discard, _} = ObanHelpers.perform(job)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
38
test/pleroma/web/masto_fe_controller_test.exs
Normal file
38
test/pleroma/web/masto_fe_controller_test.exs
Normal 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
|
|
@ -10,10 +10,11 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
test "get instance information", %{conn: conn} do
|
test "get instance information", %{conn: conn} do
|
||||||
|
clear_config([:instance, :languages], ["en", "ja"])
|
||||||
conn = get(conn, "/api/v1/instance")
|
conn = get(conn, "/api/v1/instance")
|
||||||
assert result = json_response_and_validate_schema(conn, 200)
|
assert result = json_response_and_validate_schema(conn, 200)
|
||||||
|
|
||||||
email = Pleroma.Config.get([:instance, :email])
|
email = Pleroma.Config.get([:instance, :email])
|
||||||
|
|
||||||
thumbnail = Pleroma.Web.Endpoint.url() <> Pleroma.Config.get([:instance, :instance_thumbnail])
|
thumbnail = Pleroma.Web.Endpoint.url() <> Pleroma.Config.get([:instance, :instance_thumbnail])
|
||||||
background = Pleroma.Web.Endpoint.url() <> Pleroma.Config.get([:instance, :background_image])
|
background = Pleroma.Web.Endpoint.url() <> Pleroma.Config.get([:instance, :background_image])
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ test "get instance information", %{conn: conn} do
|
||||||
},
|
},
|
||||||
"stats" => _,
|
"stats" => _,
|
||||||
"thumbnail" => from_config_thumbnail,
|
"thumbnail" => from_config_thumbnail,
|
||||||
"languages" => _,
|
"languages" => ["en", "ja"],
|
||||||
"registrations" => _,
|
"registrations" => _,
|
||||||
"approval_required" => _,
|
"approval_required" => _,
|
||||||
"poll_limits" => _,
|
"poll_limits" => _,
|
||||||
|
|
|
@ -264,6 +264,7 @@ test "posting a fake status", %{conn: conn} do
|
||||||
|> Map.put("url", nil)
|
|> Map.put("url", nil)
|
||||||
|> Map.put("uri", nil)
|
|> Map.put("uri", nil)
|
||||||
|> Map.put("created_at", nil)
|
|> Map.put("created_at", nil)
|
||||||
|
|> Kernel.put_in(["pleroma", "context"], nil)
|
||||||
|> Kernel.put_in(["pleroma", "conversation_id"], nil)
|
|> Kernel.put_in(["pleroma", "conversation_id"], nil)
|
||||||
|
|
||||||
fake_conn =
|
fake_conn =
|
||||||
|
@ -287,6 +288,7 @@ test "posting a fake status", %{conn: conn} do
|
||||||
|> Map.put("url", nil)
|
|> Map.put("url", nil)
|
||||||
|> Map.put("uri", nil)
|
|> Map.put("uri", nil)
|
||||||
|> Map.put("created_at", nil)
|
|> Map.put("created_at", nil)
|
||||||
|
|> Kernel.put_in(["pleroma", "context"], nil)
|
||||||
|> Kernel.put_in(["pleroma", "conversation_id"], nil)
|
|> Kernel.put_in(["pleroma", "conversation_id"], nil)
|
||||||
|
|
||||||
assert real_status == fake_status
|
assert real_status == fake_status
|
||||||
|
|
|
@ -44,14 +44,15 @@ test "has an emoji reaction list" do
|
||||||
assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
|
assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
|
||||||
|
|
||||||
assert status[:pleroma][:emoji_reactions] == [
|
assert status[:pleroma][:emoji_reactions] == [
|
||||||
%{name: "☕", count: 2, me: false, url: nil},
|
%{name: "☕", count: 2, me: false, url: nil, account_ids: [other_user.id, user.id]},
|
||||||
%{
|
%{
|
||||||
count: 2,
|
count: 2,
|
||||||
me: false,
|
me: false,
|
||||||
name: "dinosaur",
|
name: "dinosaur",
|
||||||
url: "http://localhost:4001/emoji/dino walking.gif"
|
url: "http://localhost:4001/emoji/dino walking.gif",
|
||||||
|
account_ids: [other_user.id, user.id]
|
||||||
},
|
},
|
||||||
%{name: "🍵", count: 1, me: false, url: nil}
|
%{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
|
||||||
]
|
]
|
||||||
|
|
||||||
status = StatusView.render("show.json", activity: activity, for: user)
|
status = StatusView.render("show.json", activity: activity, for: user)
|
||||||
|
@ -59,14 +60,15 @@ test "has an emoji reaction list" do
|
||||||
assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
|
assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
|
||||||
|
|
||||||
assert status[:pleroma][:emoji_reactions] == [
|
assert status[:pleroma][:emoji_reactions] == [
|
||||||
%{name: "☕", count: 2, me: true, url: nil},
|
%{name: "☕", count: 2, me: true, url: nil, account_ids: [other_user.id, user.id]},
|
||||||
%{
|
%{
|
||||||
count: 2,
|
count: 2,
|
||||||
me: true,
|
me: true,
|
||||||
name: "dinosaur",
|
name: "dinosaur",
|
||||||
url: "http://localhost:4001/emoji/dino walking.gif"
|
url: "http://localhost:4001/emoji/dino walking.gif",
|
||||||
|
account_ids: [other_user.id, user.id]
|
||||||
},
|
},
|
||||||
%{name: "🍵", count: 1, me: false, url: nil}
|
%{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -82,7 +84,7 @@ test "works correctly with badly formatted emojis" do
|
||||||
status = StatusView.render("show.json", activity: activity, for: user)
|
status = StatusView.render("show.json", activity: activity, for: user)
|
||||||
|
|
||||||
assert status[:pleroma][:emoji_reactions] == [
|
assert status[:pleroma][:emoji_reactions] == [
|
||||||
%{name: "☕", count: 1, me: true, url: nil}
|
%{name: "☕", count: 1, me: true, url: nil, account_ids: [user.id]}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -102,7 +104,7 @@ test "doesn't show reactions from muted and blocked users" do
|
||||||
status = StatusView.render("show.json", activity: activity)
|
status = StatusView.render("show.json", activity: activity)
|
||||||
|
|
||||||
assert status[:pleroma][:emoji_reactions] == [
|
assert status[:pleroma][:emoji_reactions] == [
|
||||||
%{name: "☕", count: 1, me: false, url: nil}
|
%{name: "☕", count: 1, me: false, url: nil, account_ids: [other_user.id]}
|
||||||
]
|
]
|
||||||
|
|
||||||
status = StatusView.render("show.json", activity: activity, for: user)
|
status = StatusView.render("show.json", activity: activity, for: user)
|
||||||
|
@ -114,19 +116,25 @@ test "doesn't show reactions from muted and blocked users" do
|
||||||
status = StatusView.render("show.json", activity: activity)
|
status = StatusView.render("show.json", activity: activity)
|
||||||
|
|
||||||
assert status[:pleroma][:emoji_reactions] == [
|
assert status[:pleroma][:emoji_reactions] == [
|
||||||
%{name: "☕", count: 2, me: false, url: nil}
|
%{
|
||||||
|
name: "☕",
|
||||||
|
count: 2,
|
||||||
|
me: false,
|
||||||
|
url: nil,
|
||||||
|
account_ids: [third_user.id, other_user.id]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
status = StatusView.render("show.json", activity: activity, for: user)
|
status = StatusView.render("show.json", activity: activity, for: user)
|
||||||
|
|
||||||
assert status[:pleroma][:emoji_reactions] == [
|
assert status[:pleroma][:emoji_reactions] == [
|
||||||
%{name: "☕", count: 1, me: false, url: nil}
|
%{name: "☕", count: 1, me: false, url: nil, account_ids: [third_user.id]}
|
||||||
]
|
]
|
||||||
|
|
||||||
status = StatusView.render("show.json", activity: activity, for: other_user)
|
status = StatusView.render("show.json", activity: activity, for: other_user)
|
||||||
|
|
||||||
assert status[:pleroma][:emoji_reactions] == [
|
assert status[:pleroma][:emoji_reactions] == [
|
||||||
%{name: "☕", count: 1, me: true, url: nil}
|
%{name: "☕", count: 1, me: true, url: nil, account_ids: [other_user.id]}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -239,7 +247,7 @@ test "a note activity" do
|
||||||
object_data = Object.normalize(note, fetch: false).data
|
object_data = Object.normalize(note, fetch: false).data
|
||||||
user = User.get_cached_by_ap_id(note.data["actor"])
|
user = User.get_cached_by_ap_id(note.data["actor"])
|
||||||
|
|
||||||
convo_id = :erlang.crc32(object_data["context"])
|
convo_id = :erlang.crc32(object_data["context"]) |> Bitwise.band(Bitwise.bnot(0x8000_0000))
|
||||||
|
|
||||||
status = StatusView.render("show.json", %{activity: note})
|
status = StatusView.render("show.json", %{activity: note})
|
||||||
|
|
||||||
|
@ -272,6 +280,7 @@ test "a note activity" do
|
||||||
spoiler_text: HTML.filter_tags(object_data["summary"]),
|
spoiler_text: HTML.filter_tags(object_data["summary"]),
|
||||||
visibility: "public",
|
visibility: "public",
|
||||||
media_attachments: [],
|
media_attachments: [],
|
||||||
|
emoji_reactions: [],
|
||||||
mentions: [],
|
mentions: [],
|
||||||
tags: [
|
tags: [
|
||||||
%{
|
%{
|
||||||
|
@ -292,6 +301,7 @@ test "a note activity" do
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
local: true,
|
local: true,
|
||||||
conversation_id: convo_id,
|
conversation_id: convo_id,
|
||||||
|
context: object_data["context"],
|
||||||
in_reply_to_account_acct: nil,
|
in_reply_to_account_acct: nil,
|
||||||
content: %{"text/plain" => HTML.strip_tags(object_data["content"])},
|
content: %{"text/plain" => HTML.strip_tags(object_data["content"])},
|
||||||
spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
|
spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
|
||||||
|
@ -418,6 +428,34 @@ test "a quote that we can't resolve" do
|
||||||
assert is_nil(status.quote)
|
assert is_nil(status.quote)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "a quote from a user we block" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
blocked_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _relationship} = User.block(user, blocked_user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(blocked_user, %{status: ":< i am ANGERY"})
|
||||||
|
{:ok, quote_activity} = CommonAPI.post(other_user, %{status: "hehe", quote_id: activity.id})
|
||||||
|
|
||||||
|
status = StatusView.render("show.json", %{activity: quote_activity, for: user})
|
||||||
|
assert is_nil(status.quote)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "a quote from a user we mute" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
blocked_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _relationship} = User.mute(user, blocked_user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(blocked_user, %{status: ":< i am ANGERY"})
|
||||||
|
{:ok, quote_activity} = CommonAPI.post(other_user, %{status: "hehe", quote_id: activity.id})
|
||||||
|
|
||||||
|
status = StatusView.render("show.json", %{activity: quote_activity, for: user})
|
||||||
|
assert is_nil(status.quote)
|
||||||
|
end
|
||||||
|
|
||||||
test "contains mentions" do
|
test "contains mentions" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
mentioned = insert(:user)
|
mentioned = insert(:user)
|
||||||
|
|
|
@ -494,6 +494,129 @@ test "renders authentication page if user is already authenticated but user requ
|
||||||
assert html_response(conn, 200) =~ ~s(type="submit")
|
assert html_response(conn, 200) =~ ~s(type="submit")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "allows access if the user has a prior authorization but is authenticated with another client",
|
||||||
|
%{
|
||||||
|
app: app,
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
|
user = insert(:user)
|
||||||
|
token = insert(:oauth_token, app: app, user: user)
|
||||||
|
|
||||||
|
other_app = insert(:oauth_app, redirect_uris: "https://other_redirect.url")
|
||||||
|
authorization = insert(:oauth_authorization, user: user, app: other_app)
|
||||||
|
_reusable_token = insert(:oauth_token, app: other_app, user: user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> AuthHelper.put_session_token(token.token)
|
||||||
|
|> AuthHelper.put_session_user(user.id)
|
||||||
|
|> get(
|
||||||
|
"/oauth/authorize",
|
||||||
|
%{
|
||||||
|
"response_type" => "code",
|
||||||
|
"client_id" => other_app.client_id,
|
||||||
|
"redirect_uri" => OAuthController.default_redirect_uri(other_app),
|
||||||
|
"scope" => "read"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert URI.decode(redirected_to(conn)) ==
|
||||||
|
"https://other_redirect.url?code=#{authorization.token}"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders login page if the user has an authorization but no token",
|
||||||
|
%{
|
||||||
|
app: app,
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
|
user = insert(:user)
|
||||||
|
token = insert(:oauth_token, app: app, user: user)
|
||||||
|
|
||||||
|
other_app = insert(:oauth_app, redirect_uris: "https://other_redirect.url")
|
||||||
|
_authorization = insert(:oauth_authorization, user: user, app: other_app)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> AuthHelper.put_session_token(token.token)
|
||||||
|
|> AuthHelper.put_session_user(user.id)
|
||||||
|
|> get(
|
||||||
|
"/oauth/authorize",
|
||||||
|
%{
|
||||||
|
"response_type" => "code",
|
||||||
|
"client_id" => other_app.client_id,
|
||||||
|
"redirect_uri" => OAuthController.default_redirect_uri(other_app),
|
||||||
|
"scope" => "read"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert html_response(conn, 200) =~ ~s(type="submit")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not reuse other people's tokens",
|
||||||
|
%{
|
||||||
|
app: app,
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
token = insert(:oauth_token, app: app, user: user)
|
||||||
|
|
||||||
|
other_app = insert(:oauth_app, redirect_uris: "https://other_redirect.url")
|
||||||
|
_authorization = insert(:oauth_authorization, user: other_user, app: other_app)
|
||||||
|
_reusable_token = insert(:oauth_token, app: other_app, user: other_user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> AuthHelper.put_session_token(token.token)
|
||||||
|
|> AuthHelper.put_session_user(user.id)
|
||||||
|
|> get(
|
||||||
|
"/oauth/authorize",
|
||||||
|
%{
|
||||||
|
"response_type" => "code",
|
||||||
|
"client_id" => other_app.client_id,
|
||||||
|
"redirect_uri" => OAuthController.default_redirect_uri(other_app),
|
||||||
|
"scope" => "read"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert html_response(conn, 200) =~ ~s(type="submit")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not reuse expired tokens",
|
||||||
|
%{
|
||||||
|
app: app,
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
|
user = insert(:user)
|
||||||
|
token = insert(:oauth_token, app: app, user: user)
|
||||||
|
|
||||||
|
other_app = insert(:oauth_app, redirect_uris: "https://other_redirect.url")
|
||||||
|
_authorization = insert(:oauth_authorization, user: user, app: other_app)
|
||||||
|
|
||||||
|
_reusable_token =
|
||||||
|
insert(:oauth_token,
|
||||||
|
app: other_app,
|
||||||
|
user: user,
|
||||||
|
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), -100)
|
||||||
|
)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> AuthHelper.put_session_token(token.token)
|
||||||
|
|> AuthHelper.put_session_user(user.id)
|
||||||
|
|> get(
|
||||||
|
"/oauth/authorize",
|
||||||
|
%{
|
||||||
|
"response_type" => "code",
|
||||||
|
"client_id" => other_app.client_id,
|
||||||
|
"redirect_uri" => OAuthController.default_redirect_uri(other_app),
|
||||||
|
"scope" => "read"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert html_response(conn, 200) =~ ~s(type="submit")
|
||||||
|
end
|
||||||
|
|
||||||
test "with existing authentication and non-OOB `redirect_uri`, redirects to app with `token` and `state` params",
|
test "with existing authentication and non-OOB `redirect_uri`, redirects to app with `token` and `state` params",
|
||||||
%{
|
%{
|
||||||
app: app,
|
app: app,
|
||||||
|
|
|
@ -31,7 +31,13 @@ test "PUT /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
|
||||||
assert to_string(activity.id) == id
|
assert to_string(activity.id) == id
|
||||||
|
|
||||||
assert result["pleroma"]["emoji_reactions"] == [
|
assert result["pleroma"]["emoji_reactions"] == [
|
||||||
%{"name" => "☕", "count" => 1, "me" => true, "url" => nil}
|
%{
|
||||||
|
"name" => "☕",
|
||||||
|
"count" => 1,
|
||||||
|
"me" => true,
|
||||||
|
"url" => nil,
|
||||||
|
"account_ids" => [other_user.id]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
|
{:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
|
||||||
|
@ -54,7 +60,8 @@ test "PUT /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
|
||||||
"name" => "dinosaur",
|
"name" => "dinosaur",
|
||||||
"count" => 1,
|
"count" => 1,
|
||||||
"me" => true,
|
"me" => true,
|
||||||
"url" => "http://localhost:4001/emoji/dino walking.gif"
|
"url" => "http://localhost:4001/emoji/dino walking.gif",
|
||||||
|
"account_ids" => [other_user.id]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -86,10 +86,12 @@ test "halts the connection when `signature` header is not present", %{conn: conn
|
||||||
test "aliases redirected /object endpoints", _ do
|
test "aliases redirected /object endpoints", _ do
|
||||||
obj = insert(:note)
|
obj = insert(:note)
|
||||||
act = insert(:note_activity, note: obj)
|
act = insert(:note_activity, note: obj)
|
||||||
params = %{"actor" => "http://mastodon.example.org/users/admin"}
|
params = %{"actor" => "someparam"}
|
||||||
path = URI.parse(obj.data["id"]).path
|
path = URI.parse(obj.data["id"]).path
|
||||||
conn = build_conn(:get, path, params)
|
conn = build_conn(:get, path, params)
|
||||||
assert ["/notice/#{act.id}"] == HTTPSignaturePlug.route_aliases(conn)
|
|
||||||
|
assert ["/notice/#{act.id}", "/notice/#{act.id}?actor=someparam"] ==
|
||||||
|
HTTPSignaturePlug.route_aliases(conn)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -157,7 +157,8 @@ test "it streams the user's post in the 'user' stream", %{user: user, token: oau
|
||||||
Streamer.get_topic_and_add_socket("user", user, oauth_token)
|
Streamer.get_topic_and_add_socket("user", user, oauth_token)
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "hey"})
|
{:ok, activity} = CommonAPI.post(user, %{status: "hey"})
|
||||||
|
|
||||||
assert_receive {:render_with_user, _, _, ^activity}
|
stream_name = "user:#{user.id}"
|
||||||
|
assert_receive {:render_with_user, _, _, ^activity, ^stream_name}
|
||||||
refute Streamer.filtered_by_user?(user, activity)
|
refute Streamer.filtered_by_user?(user, activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -168,7 +169,11 @@ test "it streams boosts of the user in the 'user' stream", %{user: user, token:
|
||||||
{:ok, activity} = CommonAPI.post(other_user, %{status: "hey"})
|
{:ok, activity} = CommonAPI.post(other_user, %{status: "hey"})
|
||||||
{:ok, announce} = CommonAPI.repeat(activity.id, user)
|
{:ok, announce} = CommonAPI.repeat(activity.id, user)
|
||||||
|
|
||||||
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce}
|
stream_name = "user:#{user.id}"
|
||||||
|
|
||||||
|
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce,
|
||||||
|
^stream_name}
|
||||||
|
|
||||||
refute Streamer.filtered_by_user?(user, announce)
|
refute Streamer.filtered_by_user?(user, announce)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -221,7 +226,11 @@ test "it streams boosts of mastodon user in the 'user' stream", %{
|
||||||
{:ok, %Pleroma.Activity{data: _data, local: false} = announce} =
|
{:ok, %Pleroma.Activity{data: _data, local: false} = announce} =
|
||||||
Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(data)
|
Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce}
|
stream_name = "user:#{user.id}"
|
||||||
|
|
||||||
|
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce,
|
||||||
|
^stream_name}
|
||||||
|
|
||||||
refute Streamer.filtered_by_user?(user, announce)
|
refute Streamer.filtered_by_user?(user, announce)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -233,7 +242,7 @@ test "it sends notify to in the 'user' stream", %{
|
||||||
Streamer.get_topic_and_add_socket("user", user, oauth_token)
|
Streamer.get_topic_and_add_socket("user", user, oauth_token)
|
||||||
Streamer.stream("user", notify)
|
Streamer.stream("user", notify)
|
||||||
|
|
||||||
assert_receive {:render_with_user, _, _, ^notify}
|
assert_receive {:render_with_user, _, _, ^notify, "user"}
|
||||||
refute Streamer.filtered_by_user?(user, notify)
|
refute Streamer.filtered_by_user?(user, notify)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -245,7 +254,7 @@ test "it sends notify to in the 'user:notification' stream", %{
|
||||||
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
|
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
|
||||||
Streamer.stream("user:notification", notify)
|
Streamer.stream("user:notification", notify)
|
||||||
|
|
||||||
assert_receive {:render_with_user, _, _, ^notify}
|
assert_receive {:render_with_user, _, _, ^notify, "user:notification"}
|
||||||
refute Streamer.filtered_by_user?(user, notify)
|
refute Streamer.filtered_by_user?(user, notify)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -291,7 +300,7 @@ test "it sends favorite to 'user:notification' stream'", %{
|
||||||
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
|
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
|
||||||
{:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id)
|
{:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id)
|
||||||
|
|
||||||
assert_receive {:render_with_user, _, "notification.json", notif}
|
assert_receive {:render_with_user, _, "notification.json", notif, "user:notification"}
|
||||||
assert notif.activity.id == favorite_activity.id
|
assert notif.activity.id == favorite_activity.id
|
||||||
refute Streamer.filtered_by_user?(user, notif)
|
refute Streamer.filtered_by_user?(user, notif)
|
||||||
end
|
end
|
||||||
|
@ -320,7 +329,7 @@ test "it sends follow activities to the 'user:notification' stream", %{
|
||||||
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
|
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
|
||||||
{:ok, _follower, _followed, follow_activity} = CommonAPI.follow(user2, user)
|
{:ok, _follower, _followed, follow_activity} = CommonAPI.follow(user2, user)
|
||||||
|
|
||||||
assert_receive {:render_with_user, _, "notification.json", notif}
|
assert_receive {:render_with_user, _, "notification.json", notif, "user:notification"}
|
||||||
assert notif.activity.id == follow_activity.id
|
assert notif.activity.id == follow_activity.id
|
||||||
refute Streamer.filtered_by_user?(user, notif)
|
refute Streamer.filtered_by_user?(user, notif)
|
||||||
end
|
end
|
||||||
|
@ -384,7 +393,7 @@ test "it sends to public (authenticated)" do
|
||||||
Streamer.get_topic_and_add_socket("public", user, oauth_token)
|
Streamer.get_topic_and_add_socket("public", user, oauth_token)
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(other_user, %{status: "Test"})
|
{:ok, activity} = CommonAPI.post(other_user, %{status: "Test"})
|
||||||
assert_receive {:render_with_user, _, _, ^activity}
|
assert_receive {:render_with_user, _, _, ^activity, "public"}
|
||||||
refute Streamer.filtered_by_user?(other_user, activity)
|
refute Streamer.filtered_by_user?(other_user, activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -436,7 +445,7 @@ test "it filters to user if recipients invalid and thread containment is enabled
|
||||||
|
|
||||||
Streamer.get_topic_and_add_socket("public", user, oauth_token)
|
Streamer.get_topic_and_add_socket("public", user, oauth_token)
|
||||||
Streamer.stream("public", activity)
|
Streamer.stream("public", activity)
|
||||||
assert_receive {:render_with_user, _, _, ^activity}
|
assert_receive {:render_with_user, _, _, ^activity, "public"}
|
||||||
assert Streamer.filtered_by_user?(user, activity)
|
assert Streamer.filtered_by_user?(user, activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -458,7 +467,7 @@ test "it sends message if recipients invalid and thread containment is disabled"
|
||||||
Streamer.get_topic_and_add_socket("public", user, oauth_token)
|
Streamer.get_topic_and_add_socket("public", user, oauth_token)
|
||||||
Streamer.stream("public", activity)
|
Streamer.stream("public", activity)
|
||||||
|
|
||||||
assert_receive {:render_with_user, _, _, ^activity}
|
assert_receive {:render_with_user, _, _, ^activity, "public"}
|
||||||
refute Streamer.filtered_by_user?(user, activity)
|
refute Streamer.filtered_by_user?(user, activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -481,7 +490,7 @@ test "it sends message if recipients invalid and thread containment is enabled b
|
||||||
Streamer.get_topic_and_add_socket("public", user, oauth_token)
|
Streamer.get_topic_and_add_socket("public", user, oauth_token)
|
||||||
Streamer.stream("public", activity)
|
Streamer.stream("public", activity)
|
||||||
|
|
||||||
assert_receive {:render_with_user, _, _, ^activity}
|
assert_receive {:render_with_user, _, _, ^activity, "public"}
|
||||||
refute Streamer.filtered_by_user?(user, activity)
|
refute Streamer.filtered_by_user?(user, activity)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -495,7 +504,7 @@ test "it filters messages involving blocked users", %{user: user, token: oauth_t
|
||||||
|
|
||||||
Streamer.get_topic_and_add_socket("public", user, oauth_token)
|
Streamer.get_topic_and_add_socket("public", user, oauth_token)
|
||||||
{:ok, activity} = CommonAPI.post(blocked_user, %{status: "Test"})
|
{:ok, activity} = CommonAPI.post(blocked_user, %{status: "Test"})
|
||||||
assert_receive {:render_with_user, _, _, ^activity}
|
assert_receive {:render_with_user, _, _, ^activity, "public"}
|
||||||
assert Streamer.filtered_by_user?(user, activity)
|
assert Streamer.filtered_by_user?(user, activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -512,17 +521,17 @@ test "it filters messages transitively involving blocked users", %{
|
||||||
|
|
||||||
{:ok, activity_one} = CommonAPI.post(friend, %{status: "hey! @#{blockee.nickname}"})
|
{:ok, activity_one} = CommonAPI.post(friend, %{status: "hey! @#{blockee.nickname}"})
|
||||||
|
|
||||||
assert_receive {:render_with_user, _, _, ^activity_one}
|
assert_receive {:render_with_user, _, _, ^activity_one, "public"}
|
||||||
assert Streamer.filtered_by_user?(blocker, activity_one)
|
assert Streamer.filtered_by_user?(blocker, activity_one)
|
||||||
|
|
||||||
{:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
|
{:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
|
||||||
|
|
||||||
assert_receive {:render_with_user, _, _, ^activity_two}
|
assert_receive {:render_with_user, _, _, ^activity_two, "public"}
|
||||||
assert Streamer.filtered_by_user?(blocker, activity_two)
|
assert Streamer.filtered_by_user?(blocker, activity_two)
|
||||||
|
|
||||||
{:ok, activity_three} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"})
|
{:ok, activity_three} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"})
|
||||||
|
|
||||||
assert_receive {:render_with_user, _, _, ^activity_three}
|
assert_receive {:render_with_user, _, _, ^activity_three, "public"}
|
||||||
assert Streamer.filtered_by_user?(blocker, activity_three)
|
assert Streamer.filtered_by_user?(blocker, activity_three)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -583,7 +592,8 @@ test "it sends wanted private posts to list", %{user: user_a, token: user_a_toke
|
||||||
visibility: "private"
|
visibility: "private"
|
||||||
})
|
})
|
||||||
|
|
||||||
assert_receive {:render_with_user, _, _, ^activity}
|
stream_name = "list:#{list.id}"
|
||||||
|
assert_receive {:render_with_user, _, _, ^activity, ^stream_name}
|
||||||
refute Streamer.filtered_by_user?(user_a, activity)
|
refute Streamer.filtered_by_user?(user_a, activity)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -601,7 +611,8 @@ test "it filters muted reblogs", %{user: user1, token: user1_token} do
|
||||||
|
|
||||||
Streamer.get_topic_and_add_socket("user", user1, user1_token)
|
Streamer.get_topic_and_add_socket("user", user1, user1_token)
|
||||||
{:ok, announce_activity} = CommonAPI.repeat(create_activity.id, user2)
|
{:ok, announce_activity} = CommonAPI.repeat(create_activity.id, user2)
|
||||||
assert_receive {:render_with_user, _, _, ^announce_activity}
|
stream_name = "user:#{user1.id}"
|
||||||
|
assert_receive {:render_with_user, _, _, ^announce_activity, ^stream_name}
|
||||||
assert Streamer.filtered_by_user?(user1, announce_activity)
|
assert Streamer.filtered_by_user?(user1, announce_activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -617,7 +628,7 @@ test "it filters reblog notification for reblog-muted actors", %{
|
||||||
Streamer.get_topic_and_add_socket("user", user1, user1_token)
|
Streamer.get_topic_and_add_socket("user", user1, user1_token)
|
||||||
{:ok, _announce_activity} = CommonAPI.repeat(create_activity.id, user2)
|
{:ok, _announce_activity} = CommonAPI.repeat(create_activity.id, user2)
|
||||||
|
|
||||||
assert_receive {:render_with_user, _, "notification.json", notif}
|
assert_receive {:render_with_user, _, "notification.json", notif, "user"}
|
||||||
assert Streamer.filtered_by_user?(user1, notif)
|
assert Streamer.filtered_by_user?(user1, notif)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -633,7 +644,7 @@ test "it send non-reblog notification for reblog-muted actors", %{
|
||||||
Streamer.get_topic_and_add_socket("user", user1, user1_token)
|
Streamer.get_topic_and_add_socket("user", user1, user1_token)
|
||||||
{:ok, _favorite_activity} = CommonAPI.favorite(user2, create_activity.id)
|
{:ok, _favorite_activity} = CommonAPI.favorite(user2, create_activity.id)
|
||||||
|
|
||||||
assert_receive {:render_with_user, _, "notification.json", notif}
|
assert_receive {:render_with_user, _, "notification.json", notif, "user"}
|
||||||
refute Streamer.filtered_by_user?(user1, notif)
|
refute Streamer.filtered_by_user?(user1, notif)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -648,7 +659,8 @@ test "it filters posts from muted threads" do
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "super hot take"})
|
{:ok, activity} = CommonAPI.post(user, %{status: "super hot take"})
|
||||||
{:ok, _} = CommonAPI.add_mute(user2, activity)
|
{:ok, _} = CommonAPI.add_mute(user2, activity)
|
||||||
|
|
||||||
assert_receive {:render_with_user, _, _, ^activity}
|
stream_name = "user:#{user2.id}"
|
||||||
|
assert_receive {:render_with_user, _, _, ^activity, ^stream_name}
|
||||||
assert Streamer.filtered_by_user?(user2, activity)
|
assert Streamer.filtered_by_user?(user2, activity)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -690,7 +702,8 @@ test "it doesn't send conversation update to the 'direct' stream when the last m
|
||||||
})
|
})
|
||||||
|
|
||||||
create_activity_id = create_activity.id
|
create_activity_id = create_activity.id
|
||||||
assert_receive {:render_with_user, _, _, ^create_activity}
|
stream_name = "direct:#{user.id}"
|
||||||
|
assert_receive {:render_with_user, _, _, ^create_activity, ^stream_name}
|
||||||
assert_receive {:text, received_conversation1}
|
assert_receive {:text, received_conversation1}
|
||||||
assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1)
|
assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1)
|
||||||
|
|
||||||
|
@ -725,8 +738,9 @@ test "it sends conversation update to the 'direct' stream when a message is dele
|
||||||
visibility: "direct"
|
visibility: "direct"
|
||||||
})
|
})
|
||||||
|
|
||||||
assert_receive {:render_with_user, _, _, ^create_activity}
|
stream_name = "direct:#{user.id}"
|
||||||
assert_receive {:render_with_user, _, _, ^create_activity2}
|
assert_receive {:render_with_user, _, _, ^create_activity, ^stream_name}
|
||||||
|
assert_receive {:render_with_user, _, _, ^create_activity2, ^stream_name}
|
||||||
assert_receive {:text, received_conversation1}
|
assert_receive {:text, received_conversation1}
|
||||||
assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1)
|
assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1)
|
||||||
assert_receive {:text, received_conversation1}
|
assert_receive {:text, received_conversation1}
|
||||||
|
@ -746,4 +760,105 @@ test "it sends conversation update to the 'direct' stream when a message is dele
|
||||||
assert last_status["id"] == to_string(create_activity.id)
|
assert last_status["id"] == to_string(create_activity.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "stop streaming if token got revoked" do
|
||||||
|
setup do
|
||||||
|
child_proc = fn start, finalize ->
|
||||||
|
fn ->
|
||||||
|
start.()
|
||||||
|
|
||||||
|
receive do
|
||||||
|
{StreamerTest, :ready} ->
|
||||||
|
assert_receive {:render_with_user, _, "update.json", _, _}
|
||||||
|
|
||||||
|
receive do
|
||||||
|
{StreamerTest, :revoked} -> finalize.()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
starter = fn user, token ->
|
||||||
|
fn -> Streamer.get_topic_and_add_socket("user", user, token) end
|
||||||
|
end
|
||||||
|
|
||||||
|
hit = fn -> assert_receive :close end
|
||||||
|
miss = fn -> refute_receive :close end
|
||||||
|
|
||||||
|
send_all = fn tasks, thing -> Enum.each(tasks, &send(&1.pid, thing)) end
|
||||||
|
|
||||||
|
%{
|
||||||
|
child_proc: child_proc,
|
||||||
|
starter: starter,
|
||||||
|
hit: hit,
|
||||||
|
miss: miss,
|
||||||
|
send_all: send_all
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "do not revoke other tokens", %{
|
||||||
|
child_proc: child_proc,
|
||||||
|
starter: starter,
|
||||||
|
hit: hit,
|
||||||
|
miss: miss,
|
||||||
|
send_all: send_all
|
||||||
|
} do
|
||||||
|
%{user: user, token: token} = oauth_access(["read"])
|
||||||
|
%{token: token2} = oauth_access(["read"], user: user)
|
||||||
|
%{user: user2, token: user2_token} = oauth_access(["read"])
|
||||||
|
|
||||||
|
post_user = insert(:user)
|
||||||
|
CommonAPI.follow(user, post_user)
|
||||||
|
CommonAPI.follow(user2, post_user)
|
||||||
|
|
||||||
|
tasks = [
|
||||||
|
Task.async(child_proc.(starter.(user, token), hit)),
|
||||||
|
Task.async(child_proc.(starter.(user, token2), miss)),
|
||||||
|
Task.async(child_proc.(starter.(user2, user2_token), miss))
|
||||||
|
]
|
||||||
|
|
||||||
|
{:ok, _} =
|
||||||
|
CommonAPI.post(post_user, %{
|
||||||
|
status: "hi"
|
||||||
|
})
|
||||||
|
|
||||||
|
send_all.(tasks, {StreamerTest, :ready})
|
||||||
|
|
||||||
|
Pleroma.Web.OAuth.Token.Strategy.Revoke.revoke(token)
|
||||||
|
|
||||||
|
send_all.(tasks, {StreamerTest, :revoked})
|
||||||
|
|
||||||
|
Enum.each(tasks, &Task.await/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "revoke all streams for this token", %{
|
||||||
|
child_proc: child_proc,
|
||||||
|
starter: starter,
|
||||||
|
hit: hit,
|
||||||
|
send_all: send_all
|
||||||
|
} do
|
||||||
|
%{user: user, token: token} = oauth_access(["read"])
|
||||||
|
|
||||||
|
post_user = insert(:user)
|
||||||
|
CommonAPI.follow(user, post_user)
|
||||||
|
|
||||||
|
tasks = [
|
||||||
|
Task.async(child_proc.(starter.(user, token), hit)),
|
||||||
|
Task.async(child_proc.(starter.(user, token), hit))
|
||||||
|
]
|
||||||
|
|
||||||
|
{:ok, _} =
|
||||||
|
CommonAPI.post(post_user, %{
|
||||||
|
status: "hi"
|
||||||
|
})
|
||||||
|
|
||||||
|
send_all.(tasks, {StreamerTest, :ready})
|
||||||
|
|
||||||
|
Pleroma.Web.OAuth.Token.Strategy.Revoke.revoke(token)
|
||||||
|
|
||||||
|
send_all.(tasks, {StreamerTest, :revoked})
|
||||||
|
|
||||||
|
Enum.each(tasks, &Task.await/1)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
25
test/pleroma/workers/receiver_worker_test.exs
Normal file
25
test/pleroma/workers/receiver_worker_test.exs
Normal 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
|
|
@ -407,6 +407,15 @@ def get("http://mastodon.example.org/users/admin", _, _, _) do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get(
|
||||||
|
"http://mastodon.example.org/users/admin/statuses/99512778738411822/replies?min_id=99512778738411824&page=true",
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_
|
||||||
|
) do
|
||||||
|
{:ok, %Tesla.Env{status: 404, body: ""}}
|
||||||
|
end
|
||||||
|
|
||||||
def get("http://mastodon.example.org/users/relay", _, _, [
|
def get("http://mastodon.example.org/users/relay", _, _, [
|
||||||
{"accept", "application/activity+json"}
|
{"accept", "application/activity+json"}
|
||||||
]) do
|
]) do
|
||||||
|
|
|
@ -5,18 +5,17 @@
|
||||||
defmodule Pleroma.Integration.WebsocketClient do
|
defmodule Pleroma.Integration.WebsocketClient do
|
||||||
# https://github.com/phoenixframework/phoenix/blob/master/test/support/websocket_client.exs
|
# https://github.com/phoenixframework/phoenix/blob/master/test/support/websocket_client.exs
|
||||||
|
|
||||||
|
use WebSockex
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Starts the WebSocket server for given ws URL. Received Socket.Message's
|
Starts the WebSocket server for given ws URL. Received Socket.Message's
|
||||||
are forwarded to the sender pid
|
are forwarded to the sender pid
|
||||||
"""
|
"""
|
||||||
def start_link(sender, url, headers \\ []) do
|
def start_link(sender, url, headers \\ []) do
|
||||||
:crypto.start()
|
WebSockex.start_link(
|
||||||
:ssl.start()
|
url,
|
||||||
|
|
||||||
:websocket_client.start_link(
|
|
||||||
String.to_charlist(url),
|
|
||||||
__MODULE__,
|
__MODULE__,
|
||||||
[sender],
|
%{sender: sender},
|
||||||
extra_headers: headers
|
extra_headers: headers
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -36,27 +35,32 @@ def send_text(server_pid, msg) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def init([sender], _conn_state) do
|
@impl true
|
||||||
{:ok, %{sender: sender}}
|
def handle_frame(frame, state) do
|
||||||
end
|
|
||||||
|
|
||||||
@doc false
|
|
||||||
def websocket_handle(frame, _conn_state, state) do
|
|
||||||
send(state.sender, frame)
|
send(state.sender, frame)
|
||||||
{:ok, state}
|
{:ok, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_disconnect(conn_status, state) do
|
||||||
|
send(state.sender, {:close, conn_status})
|
||||||
|
{:ok, state}
|
||||||
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def websocket_info({:text, msg}, _conn_state, state) do
|
@impl true
|
||||||
|
def handle_info({:text, msg}, state) do
|
||||||
{:reply, {:text, msg}, state}
|
{:reply, {:text, msg}, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
def websocket_info(:close, _conn_state, _state) do
|
@impl true
|
||||||
|
def handle_info(:close, _state) do
|
||||||
{:close, <<>>, "done"}
|
{:close, <<>>, "done"}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def websocket_terminate(_reason, _conn_state, _state) do
|
@impl true
|
||||||
|
def terminate(_reason, _state) do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue