forked from AkkomaGang/akkoma
Merge branch 'develop' of https://akkoma.dev/AkkomaGang/akkoma into akko.wtf
This commit is contained in:
commit
d6592053e9
31 changed files with 741 additions and 225 deletions
|
@ -1,4 +1,5 @@
|
|||
platform: linux/amd64
|
||||
labels:
|
||||
platform: linux/amd64
|
||||
|
||||
depends_on:
|
||||
- test
|
||||
|
@ -34,7 +35,7 @@ variables:
|
|||
- &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)"
|
||||
- &mix-clean "mix deps.clean --all && mix clean"
|
||||
|
||||
pipeline:
|
||||
steps:
|
||||
# Canonical amd64
|
||||
debian-bookworm:
|
||||
image: hexpm/elixir:1.15.4-erlang-26.0.2-debian-bookworm-20230612
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
platform: linux/arm64
|
||||
labels:
|
||||
platform: linux/aarch64
|
||||
|
||||
depends_on:
|
||||
- test
|
||||
|
@ -34,7 +35,7 @@ variables:
|
|||
- &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)"
|
||||
- &mix-clean "mix deps.clean --all && mix clean"
|
||||
|
||||
pipeline:
|
||||
steps:
|
||||
# Canonical arm64
|
||||
debian-bookworm:
|
||||
image: hexpm/elixir:1.15.4-erlang-26.0.2-debian-bookworm-20230612
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
platform: linux/amd64
|
||||
labels:
|
||||
platform: linux/amd64
|
||||
|
||||
depends_on:
|
||||
- test
|
||||
|
@ -45,7 +46,7 @@ variables:
|
|||
- &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)"
|
||||
- &mix-clean "mix deps.clean --all && mix clean"
|
||||
|
||||
pipeline:
|
||||
steps:
|
||||
docs:
|
||||
<<: *on-point-release
|
||||
secrets:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
platform: linux/amd64
|
||||
labels:
|
||||
platform: linux/amd64
|
||||
|
||||
variables:
|
||||
- &scw-secrets
|
||||
|
@ -41,9 +42,9 @@ variables:
|
|||
- &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)"
|
||||
- &mix-clean "mix deps.clean --all && mix clean"
|
||||
|
||||
pipeline:
|
||||
steps:
|
||||
lint:
|
||||
image: akkoma/ci-base:1.15-otp26
|
||||
image: akkoma/ci-base:1.16-otp26
|
||||
<<: *on-pr-open
|
||||
environment:
|
||||
MIX_ENV: test
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
platform: linux/amd64
|
||||
labels:
|
||||
platform: linux/amd64
|
||||
|
||||
depends_on:
|
||||
- lint
|
||||
|
@ -12,12 +13,6 @@ matrix:
|
|||
- 25
|
||||
- 26
|
||||
include:
|
||||
- ELIXIR_VERSION: 1.14
|
||||
OTP_VERSION: 25
|
||||
- ELIXIR_VERSION: 1.15
|
||||
OTP_VERSION: 25
|
||||
- ELIXIR_VERSION: 1.15
|
||||
OTP_VERSION: 26
|
||||
- ELIXIR_VERSION: 1.16
|
||||
OTP_VERSION: 26
|
||||
|
||||
|
@ -73,7 +68,7 @@ services:
|
|||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
||||
pipeline:
|
||||
steps:
|
||||
test:
|
||||
image: akkoma/ci-base:${ELIXIR_VERSION}-otp${OTP_VERSION}
|
||||
<<: *on-pr-open
|
||||
|
|
|
@ -11,6 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Issue allowing use of non-media objects as attachments and crashing timeline rendering
|
||||
- Issue allowing webfinger spoofing in certain situations
|
||||
|
||||
## Added
|
||||
- Implement [FEP-67ff](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md) (federation documentation)
|
||||
|
||||
## 2024.04
|
||||
|
||||
## Added
|
||||
|
|
42
FEDERATION.md
Normal file
42
FEDERATION.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
# Federation
|
||||
|
||||
## Supported federation protocols and standards
|
||||
|
||||
- [ActivityPub](https://www.w3.org/TR/activitypub/) (Server-to-Server)
|
||||
- [WebFinger](https://webfinger.net/)
|
||||
- [Http Signatures](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures)
|
||||
- [NodeInfo](https://nodeinfo.diaspora.software/)
|
||||
|
||||
## Supported FEPs
|
||||
|
||||
- [FEP-67ff: FEDERATION](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md)
|
||||
- [FEP-f1d5: NodeInfo in Fediverse Software](https://codeberg.org/fediverse/fep/src/branch/main/fep/f1d5/fep-f1d5.md)
|
||||
- [FEP-fffd: Proxy Objects](https://codeberg.org/fediverse/fep/src/branch/main/fep/fffd/fep-fffd.md)
|
||||
|
||||
## ActivityPub
|
||||
|
||||
Akkoma mostly follows the server-to-server parts of the ActivityPub standard,
|
||||
but implements quirks for Mastodon compatibility as well as Mastodon-specific
|
||||
and custom extensions.
|
||||
|
||||
See our documentation and Mastodon’s federation information
|
||||
linked further below for details on these quirks and extensions.
|
||||
|
||||
Akkoma does not perform JSON-LD processing.
|
||||
|
||||
### Required extensions
|
||||
|
||||
#### HTTP Signatures
|
||||
All AP S2S POST requests to Akkoma instances MUST be signed.
|
||||
Depending on instance configuration the same may be true for GET requests.
|
||||
|
||||
## Nodeinfo
|
||||
|
||||
Akkoma provides many additional entries in its nodeinfo response,
|
||||
see the documentation linked below for details.
|
||||
|
||||
## Additional documentation
|
||||
|
||||
- [Akkoma’s ActivityPub extensions](https://docs.akkoma.dev/develop/development/ap_extensions/)
|
||||
- [Akkoma’s nodeinfo extensions](https://docs.akkoma.dev/develop/development/nodeinfo_extensions/)
|
||||
- [Mastodon’s federation requirements](https://github.com/mastodon/mastodon/blob/main/FEDERATION.md)
|
|
@ -63,6 +63,8 @@ To add configuration to your config file, you can copy it from the base config.
|
|||
* `local_bubble`: Array of domains representing instances closely related to yours. Used to populate the `bubble` timeline. e.g `["example.com"]`, (default: `[]`)
|
||||
* `languages`: List of Language Codes used by the instance. This is used to try and set a default language from the frontend. It will try and find the first match between the languages set here and the user's browser languages. It will default to the first language in this setting if there is no match.. (default `["en"]`)
|
||||
* `export_prometheus_metrics`: Enable prometheus metrics, served at `/api/v1/akkoma/metrics`, requiring the `admin:metrics` oauth scope.
|
||||
* `privileged_staff`: Set to `true` to give moderators access to a few higher responsibility actions.
|
||||
* `federated_timeline_available`: Set to `false` to remove access to the federated timeline for all users.
|
||||
|
||||
## :database
|
||||
* `improved_hashtag_timeline`: Setting to force toggle / force disable improved hashtags timeline. `:enabled` forces hashtags to be fetched from `hashtags` table for hashtags timeline. `:disabled` forces object-embedded hashtags to be used (slower). Keep it `:auto` for automatic behaviour (it is auto-set to `:enabled` [unless overridden] when HashtagsTableMigrator completes).
|
||||
|
|
|
@ -6,37 +6,17 @@ With the `mediaproxy` function you can use nginx to cache this content, so users
|
|||
|
||||
## Activate it
|
||||
|
||||
* Edit your nginx config and add the following location to your main server block:
|
||||
```
|
||||
location /proxy {
|
||||
return 404;
|
||||
}
|
||||
```
|
||||
|
||||
* Set up a subdomain for the proxy with its nginx config on the same machine
|
||||
*(the latter is not strictly required, but for simplicity we’ll assume so)*
|
||||
* In this subdomain’s server block add
|
||||
```
|
||||
location /proxy {
|
||||
proxy_cache akkoma_media_cache;
|
||||
proxy_cache_lock on;
|
||||
proxy_pass http://localhost:4000;
|
||||
}
|
||||
```
|
||||
Also add the following on top of the configuration, outside of the `server` block:
|
||||
```
|
||||
proxy_cache_path /tmp/akkoma-media-cache levels=1:2 keys_zone=akkoma_media_cache:10m max_size=10g inactive=720m use_temp_path=off;
|
||||
```
|
||||
If you came here from one of the installation guides, take a look at the example configuration `/installation/nginx/akkoma.nginx`, where this part is already included.
|
||||
|
||||
* Edit the nginx config for the upload/MediaProxy subdomain to point to the subdomain that has been set up
|
||||
* Append the following to your `prod.secret.exs` or `dev.secret.exs` (depends on which mode your instance is running):
|
||||
```
|
||||
```elixir
|
||||
# Replace media.example.td with the subdomain you set up earlier
|
||||
config :pleroma, :media_proxy,
|
||||
enabled: true,
|
||||
proxy_opts: [
|
||||
redirect_on_failure: true
|
||||
],
|
||||
base_url: "https://cache.akkoma.social"
|
||||
base_url: "https://media.example.tld"
|
||||
```
|
||||
You **really** should use a subdomain to serve proxied files; while we will fix bugs resulting from this, serving arbitrary remote content on your main domain namespace is a significant attack surface.
|
||||
|
||||
|
|
|
@ -130,59 +130,26 @@ config :pleroma, :http_security,
|
|||
enabled: false
|
||||
```
|
||||
|
||||
Use this as the Nginx config:
|
||||
```
|
||||
proxy_cache_path /tmp/akkoma-media-cache levels=1:2 keys_zone=akkoma_media_cache:10m max_size=10g inactive=720m use_temp_path=off;
|
||||
# The above already exists in a clearnet instance's config.
|
||||
# If not, add it.
|
||||
|
||||
server {
|
||||
listen 127.0.0.1:14447;
|
||||
server_name youri2paddress;
|
||||
|
||||
# Comment to enable logs
|
||||
access_log /dev/null;
|
||||
error_log /dev/null;
|
||||
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/activity+json application/atom+xml;
|
||||
|
||||
client_max_body_size 16m;
|
||||
|
||||
location / {
|
||||
|
||||
In the Nginx config, add the following into the `location /` block:
|
||||
```nginx
|
||||
add_header X-XSS-Protection "0";
|
||||
add_header X-Permitted-Cross-Domain-Policies none;
|
||||
add_header X-Frame-Options DENY;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header Referrer-Policy same-origin;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
proxy_pass http://localhost:4000;
|
||||
|
||||
client_max_body_size 16m;
|
||||
}
|
||||
|
||||
location /proxy {
|
||||
proxy_cache akkoma_media_cache;
|
||||
proxy_cache_lock on;
|
||||
proxy_ignore_client_abort on;
|
||||
proxy_pass http://localhost:4000;
|
||||
}
|
||||
}
|
||||
```
|
||||
reload Nginx:
|
||||
|
||||
Change the `listen` directive to the following:
|
||||
```nginx
|
||||
listen 127.0.0.1:14447;
|
||||
```
|
||||
systemctl stop i2pd.service --no-block
|
||||
systemctl start i2pd.service
|
||||
|
||||
Set `server_name` to your i2p address.
|
||||
|
||||
Reload Nginx:
|
||||
```
|
||||
systemctl restart i2pd.service --no-block
|
||||
systemctl reload nginx.service
|
||||
```
|
||||
*Notice:* The stop command initiates a graceful shutdown process, i2pd stops after finishing to route transit tunnels (maximum 10 minutes).
|
||||
|
||||
|
|
|
@ -74,56 +74,23 @@ config :pleroma, :http_security,
|
|||
enabled: false
|
||||
```
|
||||
|
||||
Use this as the Nginx config:
|
||||
```
|
||||
proxy_cache_path /tmp/akkoma-media-cache levels=1:2 keys_zone=akkoma_media_cache:10m max_size=10g inactive=720m use_temp_path=off;
|
||||
# The above already exists in a clearnet instance's config.
|
||||
# If not, add it.
|
||||
|
||||
server {
|
||||
listen 127.0.0.1:8099;
|
||||
server_name youronionaddress;
|
||||
|
||||
# Comment to enable logs
|
||||
access_log /dev/null;
|
||||
error_log /dev/null;
|
||||
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/activity+json application/atom+xml;
|
||||
|
||||
client_max_body_size 16m;
|
||||
|
||||
location / {
|
||||
|
||||
In the Nginx config, add the following into the `location /` block:
|
||||
```nginx
|
||||
add_header X-XSS-Protection "0";
|
||||
add_header X-Permitted-Cross-Domain-Policies none;
|
||||
add_header X-Frame-Options DENY;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header Referrer-Policy same-origin;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
proxy_pass http://localhost:4000;
|
||||
|
||||
client_max_body_size 16m;
|
||||
}
|
||||
|
||||
location /proxy {
|
||||
proxy_cache akkoma_media_cache;
|
||||
proxy_cache_lock on;
|
||||
proxy_ignore_client_abort on;
|
||||
proxy_pass http://localhost:4000;
|
||||
}
|
||||
}
|
||||
```
|
||||
reload Nginx:
|
||||
|
||||
Change the `listen` directive to the following:
|
||||
```nginx
|
||||
listen 127.0.0.1:8099;
|
||||
```
|
||||
|
||||
Set the `server_name` to your onion address.
|
||||
|
||||
Reload Nginx:
|
||||
```
|
||||
systemctl reload nginx
|
||||
```
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
The following endpoints are additionally present into our actors.
|
||||
|
||||
- `oauthRegistrationEndpoint` (`http://litepub.social/ns#oauthRegistrationEndpoint`)
|
||||
- `uploadMedia` (`https://www.w3.org/ns/activitystreams#uploadMedia`)
|
||||
|
||||
### oauthRegistrationEndpoint
|
||||
|
||||
|
@ -12,6 +11,279 @@ Points to MastodonAPI `/api/v1/apps` for now.
|
|||
|
||||
See <https://docs.joinmastodon.org/methods/apps/>
|
||||
|
||||
## Emoji reactions
|
||||
|
||||
Emoji reactions are implemented as a new activity type `EmojiReact`.
|
||||
A single user is allowed to react multiple times with different emoji to the
|
||||
same post. However, they may only react at most once with the same emoji.
|
||||
Repeated reaction from the same user with the same emoji are to be ignored.
|
||||
Emoji reactions are also distinct from `Like` activities and a user may both
|
||||
`Like` and react to a post.
|
||||
|
||||
!!! note
|
||||
Misskey also supports emoji reactions, but the implementations differs.
|
||||
It equates likes and reactions and only allows a single reaction per post.
|
||||
|
||||
The emoji is placed in the `content` field of the activity
|
||||
and the `object` property points to the note reacting to.
|
||||
|
||||
Emoji can either be any Unicode emoji sequence or a custom emoji.
|
||||
The latter must place their shortcode, including enclosing colons,
|
||||
into `content` and put the emoji object inside the `tag` property.
|
||||
The `tag` property MAY be omitted for Unicode emoji.
|
||||
|
||||
An example reaction with a Unicode emoji:
|
||||
```json
|
||||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://example.org/schemas/litepub-0.1.jsonld",
|
||||
{
|
||||
"@language": "und"
|
||||
}
|
||||
],
|
||||
"type": "EmojiReact",
|
||||
"id": "https://example.org/activities/23143872a0346141",
|
||||
"actor": "https://example.org/users/akko",
|
||||
"nickname": "akko",
|
||||
"to": ["https://remote.example/users/diana", "https://example.org/users/akko/followers"],
|
||||
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"content": "🧡",
|
||||
"object": "https://remote.example/objects/9f0e93499d8314a9"
|
||||
}
|
||||
```
|
||||
|
||||
An example reaction with a custom emoji:
|
||||
```json
|
||||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://example.org/schemas/litepub-0.1.jsonld",
|
||||
{
|
||||
"@language": "und"
|
||||
}
|
||||
],
|
||||
"type": "EmojiReact",
|
||||
"id": "https://example.org/activities/d75586dec0541650",
|
||||
"actor": "https://example.org/users/akko",
|
||||
"nickname": "akko",
|
||||
"to": ["https://remote.example/users/diana", "https://example.org/users/akko/followers"],
|
||||
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"content": ":mouse:",
|
||||
"object": "https://remote.example/objects/9f0e93499d8314a9",
|
||||
"tag": [{
|
||||
"type": "Emoji",
|
||||
"id": null,
|
||||
"name": "mouse",
|
||||
"icon": {
|
||||
"type": "Image",
|
||||
"url": "https://example.org/emoji/mouse/mouse.png"
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
!!! note
|
||||
Although an emoji reaction can only contain a single emoji,
|
||||
for compatibility with older versions of Pleroma and Akkoma,
|
||||
it is recommended to wrap the emoji object in a single-element array.
|
||||
|
||||
When reacting with a remote custom emoji do not include the remote domain in `content`’s shortcode
|
||||
*(unlike in our REST API which needs the domain)*:
|
||||
```json
|
||||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://example.org/schemas/litepub-0.1.jsonld",
|
||||
{
|
||||
"@language": "und"
|
||||
}
|
||||
],
|
||||
"type": "EmojiReact",
|
||||
"id": "https://example.org/activities/7993dcae98d8d5ec",
|
||||
"actor": "https://example.org/users/akko",
|
||||
"nickname": "akko",
|
||||
"to": ["https://remote.example/users/diana", "https://example.org/users/akko/followers"],
|
||||
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"content": ":hug:",
|
||||
"object": "https://remote.example/objects/9f0e93499d8314a9",
|
||||
"tag": [{
|
||||
"type": "Emoji",
|
||||
"id": "https://other.example/emojis/hug",
|
||||
"name": "hug",
|
||||
"icon": {
|
||||
"type": "Image",
|
||||
"url": "https://other.example/files/b71cea432b3fad67.webp"
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
Emoji reactions can be retracted using a standard `Undo` activity:
|
||||
```json
|
||||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"http://example.org/schemas/litepub-0.1.jsonld",
|
||||
{
|
||||
"@language": "und"
|
||||
}
|
||||
],
|
||||
"type": "Undo",
|
||||
"id": "http://example.org/activities/4685792e-efb6-4309-b508-ae4f355dd695",
|
||||
"actor": "https://example.org/users/akko",
|
||||
"to": ["https://remote.example/users/diana", "https://example.org/users/akko/followers"],
|
||||
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"object": "https://example.org/activities/23143872a0346141"
|
||||
}
|
||||
```
|
||||
|
||||
## User profile backgrounds
|
||||
|
||||
Akkoma federates user profile backgrounds the same way as Sharkey.
|
||||
|
||||
An actors ActivityPub representation contains an additional
|
||||
`backgroundUrl` property containing an `Image` object. This property
|
||||
belongs to the `"sharkey": "https://joinsharkey.org/ns#"` namespace.
|
||||
|
||||
## Quote Posts
|
||||
|
||||
Akkoma allows referencing a single other note as a quote,
|
||||
which will be prominently displayed in the interface.
|
||||
|
||||
The quoted post is referenced by its ActivityPub id in the `quoteUri` property.
|
||||
|
||||
!!! note
|
||||
Old Misskey only understood and modern Misskey still prefers
|
||||
the `_misskey_quote` property for this. Similar some other older
|
||||
software used `quoteUrl` or `quoteURL`.
|
||||
All current implementations with quote support understand `quoteUri`.
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://example.org/schemas/litepub-0.1.jsonld",
|
||||
{
|
||||
"@language": "und"
|
||||
}
|
||||
],
|
||||
"type": "Note",
|
||||
"id": "https://example.org/activities/85717e587f95d5c0",
|
||||
"actor": "https://example.org/users/akko",
|
||||
"to": ["https://remote.example/users/diana", "https://example.org/users/akko/followers"],
|
||||
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"context": "https://example.org/contexts/1",
|
||||
"content": "Look at that!",
|
||||
"quoteUri": "http://remote.example/status/85717e587f95d5c0",
|
||||
"contentMap": {
|
||||
"en": "Look at that!"
|
||||
},
|
||||
"source": {
|
||||
"content": "Look at that!",
|
||||
"mediaType": "text/plain"
|
||||
},
|
||||
"published": "2024-04-06T23:40:28Z",
|
||||
"updated": "2024-04-06T23:40:28Z",
|
||||
"attachemnt": [],
|
||||
"tag": []
|
||||
}
|
||||
```
|
||||
|
||||
## Threads
|
||||
|
||||
Akkoma assigns all posts of the same thread the same `context`. This is a
|
||||
standard ActivityPub property but its meaning is left vague. Akkoma will
|
||||
always treat posts with identical `context` as part of the same thread.
|
||||
|
||||
`context` must not be assumed to hold any meaning or be dereferencable.
|
||||
|
||||
Incoming posts without `context` will be assigned a new context.
|
||||
|
||||
!!! note
|
||||
Mastodon uses the non-standard `conversation` property for the same purpose
|
||||
*(named after an older OStatus property)*. For incoming posts without
|
||||
`context` but with `converstions` Akkoma will use the value from
|
||||
`conversations` to fill in `context`.
|
||||
For outgoing posts Akkoma will duplicate the context into `conversation`.
|
||||
|
||||
## Post Source
|
||||
|
||||
Unlike Mastodon, Akkoma supports drafting posts in multiple source formats
|
||||
besides plaintext, like Markdown or MFM. The original input is preserved
|
||||
in the standard ActivityPub `source` property *(not supported by Mastodon)*.
|
||||
Still, `content` will always be present and contain the prerendered HTML form.
|
||||
|
||||
Supported `mediaType` include:
|
||||
- `text/plain`
|
||||
- `text/markdown`
|
||||
- `text/bbcode`
|
||||
- `text/x.misskeymarkdown`
|
||||
|
||||
## Post Language
|
||||
|
||||
!!! note
|
||||
This is also supported in and compatible with Mastodon, but since
|
||||
joinmastodon.org doesn’t document it yet it is included here.
|
||||
[GoToSocial](https://docs.gotosocial.org/en/latest/federation/federating_with_gotosocial/#content-contentmap-and-language)
|
||||
has a more refined version of this which can correctly deal with multiple language entries.
|
||||
|
||||
A post can indicate its language by including a `contentMap` object
|
||||
which contains a sub key named after the language’s ISO 639-1 code
|
||||
and it’s content identical to the post’s `content` field.
|
||||
|
||||
Currently Akkoma, just like Mastodon, only properly supports a single language entry,
|
||||
in case of multiple entries a random language will be picked.
|
||||
Furthermore, Akkoma currently only reads the `content` field
|
||||
and never the value from `contentMap`.
|
||||
|
||||
## Local post scope
|
||||
|
||||
Post using this scope will never federate to other servers
|
||||
but for the sake of completeness it is listed here.
|
||||
|
||||
In addition to the usual scopes *(public, unlisted, followers-only, direct)*
|
||||
Akkoma supports an “unlisted” post scope. Such posts will not federate to
|
||||
other instances and only be shown to logged-in users on the same instance.
|
||||
It is included into the local timeline.
|
||||
This may be useful to discuss or announce instance-specific policies and topics.
|
||||
|
||||
A post is addressed to the local scope by including `<base url of instance>/#Public`
|
||||
in its `to` field. E.g. if the instance is on `https://example.org` it would use
|
||||
`https://example.org/#Public`.
|
||||
|
||||
An implementation creating a new post MUST NOT address both the local and
|
||||
general public scope `as:Public` at the same time. A post addressing the local
|
||||
scope MUST NOT be sent to other instances or be possible to fetch by other
|
||||
instances regardless of potential other listed addressees.
|
||||
|
||||
When receiving a remote post addressing both the public scope and what appears
|
||||
to be a local-scope identifier, the post SHOULD be treated without assigning any
|
||||
special meaning to the potential local-scope identifier.
|
||||
|
||||
!!! note
|
||||
Misskey-derivatives have a similar concept of non-federated posts,
|
||||
however those are also shown publicly on the local web interface
|
||||
and are thus visible to non-members.
|
||||
|
||||
## List post scope
|
||||
|
||||
Messages originally addressed to a custom list will contain
|
||||
a `listMessage` field with an unresolvable pseudo ActivityPub id.
|
||||
|
||||
# Deprecated and Removed Extensions
|
||||
|
||||
The following extensions were used in the past but have been dropped.
|
||||
Documentation is retained here as a reference and since old objects might
|
||||
still contains related fields.
|
||||
|
||||
## Actor endpoints
|
||||
|
||||
The following endpoints used to be present:
|
||||
|
||||
- `uploadMedia` (`https://www.w3.org/ns/activitystreams#uploadMedia`)
|
||||
|
||||
### uploadMedia
|
||||
|
||||
Inspired by <https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload>, it is part of the ActivityStreams namespace because it used to be part of the ActivityPub specification and got removed from it.
|
||||
|
@ -20,9 +292,8 @@ Content-Type: multipart/form-data
|
|||
|
||||
Parameters:
|
||||
- (required) `file`: The file being uploaded
|
||||
- (optionnal) `description`: A plain-text description of the media, for accessibility purposes.
|
||||
- (optional) `description`: A plain-text description of the media, for accessibility purposes.
|
||||
|
||||
Response: HTTP 201 Created with the object into the body, no `Location` header provided as it doesn't have an `id`
|
||||
|
||||
The object given in the reponse should then be inserted into an Object's `attachment` field.
|
||||
|
||||
The object given in the response should then be inserted into an Object's `attachment` field.
|
||||
|
|
141
docs/docs/development/nodeinfo_extensions.md
Normal file
141
docs/docs/development/nodeinfo_extensions.md
Normal file
|
@ -0,0 +1,141 @@
|
|||
# Nodeinfo Extensions
|
||||
|
||||
Akkoma currently implements version 2.0 and 2.1 of nodeinfo spec,
|
||||
but provides the following additional fields.
|
||||
|
||||
## metadata
|
||||
|
||||
The spec leaves the content of `metadata` up to implementations
|
||||
and indeed Akkoma adds many fields here apart from the commonly
|
||||
found `nodeName` and `nodeDescription` fields.
|
||||
|
||||
### accountActivationRequired
|
||||
Whether or not users need to confirm their email before completing registration.
|
||||
*(boolean)*
|
||||
|
||||
!!! note
|
||||
Not to be confused with account approval, where each registration needs to
|
||||
be manually approved by an admin. Account approval has no nodeinfo entry.
|
||||
|
||||
### features
|
||||
|
||||
Array of strings denoting supported server features. E.g. a server supporting
|
||||
quote posts should include a `"quote_posting"` entry here.
|
||||
|
||||
A non-exhaustive list of possible features:
|
||||
- `polls`
|
||||
- `quote_posting`
|
||||
- `editing`
|
||||
- `bubble_timeline`
|
||||
- `pleroma_emoji_reactions` *(Unicode emoji)*
|
||||
- `custom_emoji_reactions`
|
||||
- `akkoma_api`
|
||||
- `akkoma:machine_translation`
|
||||
- `mastodon_api`
|
||||
- `pleroma_api`
|
||||
|
||||
### federatedTimelineAvailable
|
||||
Whether or not the “federated timeline”, i.e. a timeline containing posts from
|
||||
the entire known network, is made available.
|
||||
*(boolean)*
|
||||
|
||||
### federation
|
||||
This section is optional and can contain various custom keys describing federation policies.
|
||||
The following are required to be presented:
|
||||
- `enabled` *(boolean)* whether the server federates at all
|
||||
|
||||
A non-exhaustive list of optional keys:
|
||||
- `exclusions` *(boolean)* whether some federation policies are withheld
|
||||
- `mrf_simple` *(object)* describes how the Simple MRF policy is configured
|
||||
|
||||
### fieldsLimits
|
||||
A JSON object documenting restriction for user account info fields.
|
||||
All properties are integers.
|
||||
|
||||
- `maxFields` maximum number of account info fields local users can create
|
||||
- `maxRemoteFields` maximum number of account info fields remote users can have
|
||||
before the user gets rejected or fields truncated
|
||||
- `nameLength` maximum length of a field’s name
|
||||
- `valueLength` maximum length of a field’s value
|
||||
|
||||
### invitesEnabled
|
||||
Whether or not signing up via invite codes is possible.
|
||||
*(boolean)*
|
||||
|
||||
### localBubbleInstances
|
||||
Array of domains (as strings) of other instances chosen
|
||||
by the admin which are shown in the bubble timeline.
|
||||
|
||||
### mailerEnabled
|
||||
Whether or not the instance can send out emails.
|
||||
*(boolean)*
|
||||
|
||||
### nodeDescription
|
||||
Human-friendly description of this instance
|
||||
*(string)*
|
||||
|
||||
### nodeName
|
||||
Human-friendly name of this instance
|
||||
*(string)*
|
||||
|
||||
### pollLimits
|
||||
JSON object containing limits for polls created by local users.
|
||||
All values are integers.
|
||||
- `max_options` maximum number of poll options
|
||||
- `max_option_chars` maximum characters per poll option
|
||||
- `min_expiration` minimum time in seconds a poll must be open for
|
||||
- `max_expiration` maximum time a poll is allowed to be open for
|
||||
|
||||
### postFormats
|
||||
Array of strings containing media types for supported post source formats.
|
||||
A non-exhaustive list of possible values:
|
||||
- `text/plain`
|
||||
- `text/markdown`
|
||||
- `text/bbcode`
|
||||
- `text/x.misskeymarkdown`
|
||||
|
||||
### private
|
||||
Whether or not unauthenticated API access is permitted.
|
||||
*(boolean)*
|
||||
|
||||
### privilegedStaff
|
||||
Whether or not moderators are trusted to perform some
|
||||
additional tasks like e.g. issuing password reset emails.
|
||||
|
||||
### publicTimelineVisibility
|
||||
JSON object containing boolean-valued keys reporting
|
||||
if a given timeline can be viewed without login.
|
||||
- `local`
|
||||
- `federated`
|
||||
- `bubble`
|
||||
|
||||
### restrictedNicknames
|
||||
Array of strings listing nicknames forbidden to be used during signup.
|
||||
|
||||
### skipThreadContainment
|
||||
Whether broken threads are filtered out
|
||||
*(boolean)*
|
||||
|
||||
### staffAccounts
|
||||
Array containing ActivityPub IDs of local accounts
|
||||
with some form of elevated privilege on the instance.
|
||||
|
||||
### suggestions
|
||||
JSON object containing info on whether the interaction-based
|
||||
Mastodon `/api/v1/suggestions` feature is enabled and optionally
|
||||
additional implementation-defined fields with more details
|
||||
on e.g. how suggested users are selected.
|
||||
|
||||
!!! note
|
||||
This has no relation to the newer /api/v2/suggestions API
|
||||
which also (or exclusively) contains staff-curated entries.
|
||||
|
||||
- `enabled` *(boolean)* whether or not user recommendations are enabled
|
||||
|
||||
### uploadLimits
|
||||
JSON object documenting various upload-related size limits.
|
||||
All values are integers and in bytes.
|
||||
- `avatar` maximum size of uploaded user avatars
|
||||
- `banner` maximum size of uploaded user profile banners
|
||||
- `background` maximum size of uploaded user profile backgrounds
|
||||
- `general` maximum size for all other kinds of uploads
|
|
@ -60,7 +60,7 @@ ServerTokens Prod
|
|||
Include /etc/letsencrypt/options-ssl-apache.conf
|
||||
|
||||
# Uncomment the following to enable MediaProxy caching on disk
|
||||
#CacheRoot /tmp/akkoma-media-cache/
|
||||
#CacheRoot /var/tmp/akkoma-media-cache/
|
||||
#CacheDirLevels 1
|
||||
#CacheDirLength 2
|
||||
#CacheEnable disk /proxy
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
SCRIPTNAME=${0##*/}
|
||||
|
||||
# mod_disk_cache directory
|
||||
CACHE_DIRECTORY="/tmp/akkoma-media-cache"
|
||||
CACHE_DIRECTORY="/var/tmp/akkoma-media-cache"
|
||||
|
||||
## Removes an item via the htcacheclean utility
|
||||
## $1 - the filename, can be a pattern .
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# See the documentation at docs.akkoma.dev for your particular distro/OS for
|
||||
# installation instructions.
|
||||
|
||||
proxy_cache_path /tmp/akkoma-media-cache levels=1:2 keys_zone=akkoma_media_cache:10m max_size=1g
|
||||
proxy_cache_path /var/tmp/akkoma-media-cache levels=1:2 keys_zone=akkoma_media_cache:10m max_size=1g
|
||||
inactive=720m use_temp_path=off;
|
||||
|
||||
# this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
SCRIPTNAME=${0##*/}
|
||||
|
||||
# NGINX cache directory
|
||||
CACHE_DIRECTORY="/tmp/akkoma-media-cache"
|
||||
CACHE_DIRECTORY="/var/tmp/akkoma-media-cache"
|
||||
|
||||
## Return the files where the items are cached.
|
||||
## $1 - the filename, can be a pattern .
|
||||
|
|
|
@ -1545,11 +1545,19 @@ defp normalize_also_known_as(aka) when is_list(aka), do: aka
|
|||
defp normalize_also_known_as(aka) when is_binary(aka), do: [aka]
|
||||
defp normalize_also_known_as(nil), do: []
|
||||
|
||||
defp normalize_attachment(%{} = attachment), do: [attachment]
|
||||
defp normalize_attachment(attachment) when is_list(attachment), do: attachment
|
||||
defp normalize_attachment(_), do: []
|
||||
|
||||
defp object_to_user_data(data, additional) do
|
||||
fields =
|
||||
data
|
||||
|> Map.get("attachment", [])
|
||||
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
||||
|> normalize_attachment()
|
||||
|> Enum.filter(fn
|
||||
%{"type" => t} -> t == "PropertyValue"
|
||||
_ -> false
|
||||
end)
|
||||
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
||||
|
||||
emojis =
|
||||
|
|
64
priv/repo/migrations/20240501190000_drop_unused_indexes.exs
Normal file
64
priv/repo/migrations/20240501190000_drop_unused_indexes.exs
Normal file
|
@ -0,0 +1,64 @@
|
|||
defmodule Pleroma.Repo.Migrations.DropUnusedIndexes do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
# Leftovers from a late Pleroma migration (will not be restored on rollback)
|
||||
drop_i(:users, [:show_birthday], :users_show_birthday_index)
|
||||
|
||||
drop_i(
|
||||
:users,
|
||||
["date_part('month', birthday)", "date_part('day', birthday)"],
|
||||
:users_birthday_month_day_index
|
||||
)
|
||||
|
||||
# Unused
|
||||
drop_i(:activities, ["(data->'cc')"], :activities_cc_index)
|
||||
drop_i(:activities, ["(data->'object'->>'inReplyTo')"], :activities_in_reply_to)
|
||||
drop_i(:activities, ["(data #> '{\"object\",\"likes\"}')"], :activities_likes)
|
||||
drop_i(:activities, ["(data->'to')"], :activities_to_index)
|
||||
|
||||
drop_i(:objects, ["(data->'likes')"], :objects_likes)
|
||||
|
||||
drop_i(:users, [:featured_address], :users_featured_address_index)
|
||||
drop_i(:users, [:following_address], :users_following_address_index)
|
||||
drop_i(:users, [:invisible], :users_invisible_index)
|
||||
drop_i(:users, [:last_status_at], :users_last_status_at_index)
|
||||
drop_i(:users, [:tags], :users_tags_index)
|
||||
|
||||
drop_i(:apps, [:client_id, :client_secret], :apps_client_id_client_secret_index)
|
||||
drop_i(:apps, [:user_id], :apps_user_id_index)
|
||||
|
||||
# Duplicate of primary key index (will not be restored on rollback)
|
||||
drop_i(
|
||||
:user_frontend_setting_profiles,
|
||||
[:user_id, :frontend_name, :profile_name],
|
||||
:user_frontend_setting_profiles_user_id_frontend_name_profile_name_index
|
||||
)
|
||||
end
|
||||
|
||||
def down do
|
||||
create_i(:activities, ["(data->'cc')"], :activities_cc_index, :gin)
|
||||
create_i(:activities, ["(data->'object'->>'inReplyTo')"], :activities_in_reply_to)
|
||||
create_i(:activities, ["(data #> '{\"object\",\"likes\"}')"], :activities_likes, :gin)
|
||||
create_i(:activities, ["(data->'to')"], :activities_to_index, :gin)
|
||||
|
||||
create_i(:objects, ["(data->'likes')"], :objects_likes, :gin)
|
||||
|
||||
create_i(:users, [:featured_address], :users_featured_address_index)
|
||||
create_i(:users, [:following_address], :users_following_address_index)
|
||||
create_i(:users, [:invisible], :users_invisible_index)
|
||||
create_i(:users, [:last_status_at], :users_last_status_at_index)
|
||||
create_i(:users, [:tags], :users_tags_index, :gin)
|
||||
|
||||
create_i(:apps, [:client_id, :client_secret], :apps_client_id_client_secret_index)
|
||||
create_i(:apps, [:user_id], :apps_user_id_index)
|
||||
end
|
||||
|
||||
defp drop_i(table, fields, name) do
|
||||
drop_if_exists(index(table, fields, name: name))
|
||||
end
|
||||
|
||||
defp create_i(table, fields, name, type \\ :btree) do
|
||||
create_if_not_exists(index(table, fields, name: name, using: type))
|
||||
end
|
||||
end
|
|
@ -1,74 +0,0 @@
|
|||
defmodule Pleroma.Repo.Migrations.DropUnusedIndexes do
|
||||
use Ecto.Migration
|
||||
|
||||
@disable_ddl_transaction true
|
||||
|
||||
@disable_migration_lock true
|
||||
|
||||
def up do
|
||||
drop_if_exists(
|
||||
index(:activities, ["(data->>'actor')", "inserted_at desc"], name: :activities_actor_index)
|
||||
)
|
||||
|
||||
drop_if_exists(index(:activities, ["(data->'to')"], name: :activities_to_index))
|
||||
|
||||
drop_if_exists(index(:activities, ["(data->'cc')"], name: :activities_cc_index))
|
||||
|
||||
drop_if_exists(index(:activities, ["(split_part(actor, '/', 3))"], name: :activities_hosts))
|
||||
|
||||
drop_if_exists(
|
||||
index(:activities, ["(data->'object'->>'inReplyTo')"], name: :activities_in_reply_to)
|
||||
)
|
||||
|
||||
drop_if_exists(
|
||||
index(:activities, ["((data #> '{\"object\",\"likes\"}'))"], name: :activities_likes)
|
||||
)
|
||||
end
|
||||
|
||||
def down do
|
||||
create_if_not_exists(
|
||||
index(:activities, ["(data->>'actor')", "inserted_at desc"],
|
||||
name: :activities_actor_index,
|
||||
concurrently: true
|
||||
)
|
||||
)
|
||||
|
||||
create_if_not_exists(
|
||||
index(:activities, ["(data->'to')"],
|
||||
name: :activities_to_index,
|
||||
using: :gin,
|
||||
concurrently: true
|
||||
)
|
||||
)
|
||||
|
||||
create_if_not_exists(
|
||||
index(:activities, ["(data->'cc')"],
|
||||
name: :activities_cc_index,
|
||||
using: :gin,
|
||||
concurrently: true
|
||||
)
|
||||
)
|
||||
|
||||
create_if_not_exists(
|
||||
index(:activities, ["(split_part(actor, '/', 3))"],
|
||||
name: :activities_hosts,
|
||||
concurrently: true
|
||||
)
|
||||
)
|
||||
|
||||
create_if_not_exists(
|
||||
index(:activities, ["(data->'object'->>'inReplyTo')"],
|
||||
name: :activities_in_reply_to,
|
||||
concurrently: true
|
||||
)
|
||||
)
|
||||
|
||||
create_if_not_exists(
|
||||
index(:activities, ["((data #> '{\"object\",\"likes\"}'))"],
|
||||
name: :activities_likes,
|
||||
using: :gin,
|
||||
concurrently: true
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
51
test/fixtures/users_mock/nonsense_attachment_user.json
vendored
Normal file
51
test/fixtures/users_mock/nonsense_attachment_user.json
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
{
|
||||
"blurhash": "toot:blurhash",
|
||||
"Emoji": "toot:Emoji",
|
||||
"focalPoint": {
|
||||
"@container": "@list",
|
||||
"@id": "toot:focalPoint"
|
||||
},
|
||||
"Hashtag": "as:Hashtag",
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"sensitive": "as:sensitive",
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"votersCount": "toot:votersCount",
|
||||
"featured": {
|
||||
"@id": "toot:featured",
|
||||
"@type": "@id"
|
||||
}
|
||||
},
|
||||
"https://w3id.org/security/v1"
|
||||
],
|
||||
"id": "https://fedi.vision/@vote@fedi.vision/",
|
||||
"type": "Person",
|
||||
"toot:discoverable": true,
|
||||
"inbox": "https://fedi.vision/@vote@fedi.vision/inbox/",
|
||||
"publicKey": {
|
||||
"id": "https://fedi.vision/@vote@fedi.vision/#main-key",
|
||||
"owner": "https://fedi.vision/@vote@fedi.vision/",
|
||||
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj2f+uQtdoBO9X/u2Qso4\nxHYdfy8zB24m9Gg982/ts88DAMLxZUzX0JsBWT7coL0Ipf4NSbVaqS6nKrr2P8Qs\nf97wMhowyuYxK22BMPcbpfZkFj3tVT/JkDx2iujBJJ5ZBO5KRlupjDTqV4rOAY7F\n58ad0jK9PsJNJMsJ/b8+0t3Q/K+RqCGVmtK+iPSigOYoiKoquyRzHLTfP+mpOlDa\n3f+uyAbFya7CpcgBx1zz0PALWA+oh/zhZK4yT6719Esa8SDcoJ0ws70zMxWekq1A\n3ia88/Io6SY2qFNBpzzXGO3JK8OFRFtmPV8ZfAh5Pv6y52iuTJ21kxjAG7ZTP/fY\nBQIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||
},
|
||||
"attachment": {
|
||||
"haha": "you expected a proper object, but it was me, random nonsense"
|
||||
},
|
||||
"endpoints": {
|
||||
"sharedInbox": "https://fedi.vision/inbox/"
|
||||
},
|
||||
"followers": "https://fedi.vision/@vote@fedi.vision/followers/",
|
||||
"following": "https://fedi.vision/@vote@fedi.vision/following/",
|
||||
"icon": {
|
||||
"type": "Image",
|
||||
"mediaType": "image/webp",
|
||||
"url": "https://eu-central-1.linodeobjects.com:443/st4/profile_images/2024/5/9/RwqTbeYx16gauXPXvt-CaysOnGw.webp"
|
||||
},
|
||||
"name": "FediVision Vote Bot",
|
||||
"outbox": "https://fedi.vision/@vote@fedi.vision/outbox/",
|
||||
"preferredUsername": "vote",
|
||||
"published": "2024-05-09T09:04:04Z",
|
||||
"summary": "<p>New in 2024, this is the bot that will count your #Fedivision vote! Accept no substitutes!</p><p>Send this account a toot in the form<br> vote ABCD EFGH IJKL<br>substituting the (up to) three codes for the songs you want to win. Punctuation ignored, case insensitive, order is unimportant. Only your latest toot counts, so change your vote with a new toot.</p>",
|
||||
"url": "https://fedi.vision/@vote/"
|
||||
}
|
53
test/fixtures/users_mock/takahe_user.json
vendored
Normal file
53
test/fixtures/users_mock/takahe_user.json
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
{
|
||||
"blurhash": "toot:blurhash",
|
||||
"Emoji": "toot:Emoji",
|
||||
"focalPoint": {
|
||||
"@container": "@list",
|
||||
"@id": "toot:focalPoint"
|
||||
},
|
||||
"Hashtag": "as:Hashtag",
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"sensitive": "as:sensitive",
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"votersCount": "toot:votersCount",
|
||||
"featured": {
|
||||
"@id": "toot:featured",
|
||||
"@type": "@id"
|
||||
}
|
||||
},
|
||||
"https://w3id.org/security/v1"
|
||||
],
|
||||
"id": "https://fedi.vision/@vote@fedi.vision/",
|
||||
"type": "Person",
|
||||
"toot:discoverable": true,
|
||||
"inbox": "https://fedi.vision/@vote@fedi.vision/inbox/",
|
||||
"publicKey": {
|
||||
"id": "https://fedi.vision/@vote@fedi.vision/#main-key",
|
||||
"owner": "https://fedi.vision/@vote@fedi.vision/",
|
||||
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj2f+uQtdoBO9X/u2Qso4\nxHYdfy8zB24m9Gg982/ts88DAMLxZUzX0JsBWT7coL0Ipf4NSbVaqS6nKrr2P8Qs\nf97wMhowyuYxK22BMPcbpfZkFj3tVT/JkDx2iujBJJ5ZBO5KRlupjDTqV4rOAY7F\n58ad0jK9PsJNJMsJ/b8+0t3Q/K+RqCGVmtK+iPSigOYoiKoquyRzHLTfP+mpOlDa\n3f+uyAbFya7CpcgBx1zz0PALWA+oh/zhZK4yT6719Esa8SDcoJ0ws70zMxWekq1A\n3ia88/Io6SY2qFNBpzzXGO3JK8OFRFtmPV8ZfAh5Pv6y52iuTJ21kxjAG7ZTP/fY\nBQIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||
},
|
||||
"attachment": {
|
||||
"type": "PropertyValue",
|
||||
"value": "<a href=\"https://fedivision.party/vote\" rel=\"nofollow\"><span class=\"invisible\">https://</span>fedivision.party/vote</a>",
|
||||
"name": "More details"
|
||||
},
|
||||
"endpoints": {
|
||||
"sharedInbox": "https://fedi.vision/inbox/"
|
||||
},
|
||||
"followers": "https://fedi.vision/@vote@fedi.vision/followers/",
|
||||
"following": "https://fedi.vision/@vote@fedi.vision/following/",
|
||||
"icon": {
|
||||
"type": "Image",
|
||||
"mediaType": "image/webp",
|
||||
"url": "https://eu-central-1.linodeobjects.com:443/st4/profile_images/2024/5/9/RwqTbeYx16gauXPXvt-CaysOnGw.webp"
|
||||
},
|
||||
"name": "FediVision Vote Bot",
|
||||
"outbox": "https://fedi.vision/@vote@fedi.vision/outbox/",
|
||||
"preferredUsername": "vote",
|
||||
"published": "2024-05-09T09:04:04Z",
|
||||
"summary": "<p>New in 2024, this is the bot that will count your #Fedivision vote! Accept no substitutes!</p><p>Send this account a toot in the form<br> vote ABCD EFGH IJKL<br>substituting the (up to) three codes for the songs you want to win. Punctuation ignored, case insensitive, order is unimportant. Only your latest toot counts, so change your vote with a new toot.</p>",
|
||||
"url": "https://fedi.vision/@vote/"
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.AppTest do
|
||||
use Pleroma.DataCase, async: true
|
||||
use Pleroma.DataCase, async: false
|
||||
|
||||
setup_all do
|
||||
Mix.shell(Mix.Shell.Process)
|
||||
|
@ -50,13 +50,13 @@ test "with errors" do
|
|||
defp assert_app(name, redirect, scopes) do
|
||||
app = Repo.get_by(Pleroma.Web.OAuth.App, client_name: name)
|
||||
|
||||
assert_receive {:mix_shell, :info, [message]}, 1_000
|
||||
assert_receive {:mix_shell, :info, [message]}, 5_000
|
||||
assert message == "#{name} successfully created:"
|
||||
|
||||
assert_receive {:mix_shell, :info, [message]}, 1_000
|
||||
assert_receive {:mix_shell, :info, [message]}, 5_000
|
||||
assert message == "App client_id: #{app.client_id}"
|
||||
|
||||
assert_receive {:mix_shell, :info, [message]}, 1_000
|
||||
assert_receive {:mix_shell, :info, [message]}, 5_000
|
||||
assert message == "App client_secret: #{app.client_secret}"
|
||||
|
||||
assert app.scopes == scopes
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.DatabaseTest do
|
||||
use Pleroma.DataCase, async: true
|
||||
use Pleroma.DataCase, async: false
|
||||
use Oban.Testing, repo: Pleroma.Repo
|
||||
|
||||
alias Pleroma.Activity
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.Ecto.RollbackTest do
|
||||
use Pleroma.DataCase, async: true
|
||||
use Pleroma.DataCase, async: false
|
||||
import ExUnit.CaptureLog
|
||||
require Logger
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.EctoTest do
|
||||
use ExUnit.Case, async: true
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
test "raise on bad path" do
|
||||
assert_raise RuntimeError, ~r/Could not find migrations directory/, fn ->
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.EmojiTest do
|
||||
use ExUnit.Case, async: true
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
import ExUnit.CaptureIO
|
||||
import Tesla.Mock
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Emoji.FormatterTest do
|
||||
alias Pleroma.Emoji.Formatter
|
||||
use Pleroma.DataCase, async: true
|
||||
use Pleroma.DataCase, async: false
|
||||
|
||||
describe "emojify" do
|
||||
test "it adds cool emoji" do
|
||||
|
|
|
@ -4,7 +4,7 @@ defmodule Pleroma.HTTP.BackoffTest do
|
|||
alias Pleroma.HTTP.Backoff
|
||||
|
||||
defp within_tolerance?(ttl, expected) do
|
||||
ttl > expected - 10 and ttl < expected + 10
|
||||
ttl > expected - 15 and ttl < expected + 15
|
||||
end
|
||||
|
||||
describe "get/3" do
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Upload.Filter.Exiftool.ReadDescriptionTest do
|
||||
use Pleroma.DataCase, async: true
|
||||
use Pleroma.DataCase, async: false
|
||||
alias Pleroma.Upload.Filter
|
||||
|
||||
@uploads %Pleroma.Upload{
|
||||
|
|
|
@ -233,6 +233,48 @@ test "works for bridgy actors" do
|
|||
}
|
||||
end
|
||||
|
||||
test "works for takahe actors" do
|
||||
user_id = "https://fedi.vision/@vote@fedi.vision/"
|
||||
|
||||
Tesla.Mock.mock(fn
|
||||
%{method: :get, url: ^user_id} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/users_mock/takahe_user.json"),
|
||||
headers: [{"content-type", "application/activity+json"}]
|
||||
}
|
||||
end)
|
||||
|
||||
{:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
|
||||
|
||||
assert user.actor_type == "Person"
|
||||
|
||||
assert [
|
||||
%{
|
||||
"name" => "More details"
|
||||
}
|
||||
] = user.fields
|
||||
end
|
||||
|
||||
test "works for actors with malformed attachment fields" do
|
||||
user_id = "https://fedi.vision/@vote@fedi.vision/"
|
||||
|
||||
Tesla.Mock.mock(fn
|
||||
%{method: :get, url: ^user_id} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/users_mock/nonsense_attachment_user.json"),
|
||||
headers: [{"content-type", "application/activity+json"}]
|
||||
}
|
||||
end)
|
||||
|
||||
{:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
|
||||
|
||||
assert user.actor_type == "Person"
|
||||
|
||||
assert [] = user.fields
|
||||
end
|
||||
|
||||
test "fetches user featured collection" do
|
||||
ap_id = "https://example.com/users/lain"
|
||||
|
||||
|
|
Loading…
Reference in a new issue