Compare commits

..

No commits in common. "develop" and "develop" have entirely different histories.

153 changed files with 2070 additions and 13359 deletions

1
.gitignore vendored
View File

@ -78,4 +78,3 @@ docs/venv
# docker stuff
docker-db
*.iml
docker-compose.override.yml

View File

@ -7,7 +7,6 @@ matrix:
ELIXIR_VERSION:
- 1.14
- 1.15
- 1.16
OTP_VERSION:
- 25
- 26
@ -18,8 +17,6 @@ matrix:
OTP_VERSION: 25
- ELIXIR_VERSION: 1.15
OTP_VERSION: 26
- ELIXIR_VERSION: 1.16
OTP_VERSION: 26
variables:
- &scw-secrets

View File

@ -6,85 +6,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased
## 2024.04
## Added
- Support for [FEP-fffd](https://codeberg.org/fediverse/fep/src/branch/main/fep/fffd/fep-fffd.md) (proxy objects)
- Verified support for elixir 1.16
- Uploadfilter `Pleroma.Upload.Filter.Exiftool.ReadDescription` returns description values to the FE so they can pre fill the image description field
NOTE: this filter MUST be placed before `Exiftool.StripMetadata` to work
## Changed
- Inbound pipeline error handing was modified somewhat, which should lead to less incomprehensible log spam. Hopefully.
- Uploadfilter `Pleroma.Upload.Filter.Exiftool` was replaced by `Pleroma.Upload.Filter.Exiftool.StripMetadata`;
the latter strips all non-essential metadata by default but can be configured.
To regain the old behaviour of only stripping GPS data set `purge: ["gps:all"]`.
- Uploadfilter `Pleroma.Upload.Filter.Exiftool` has been renamed to `Pleroma.Upload.Filter.Exiftool.StripMetadata`
- MRF.InlineQuotePolicy now prefers to insert display URLs instead of ActivityPub IDs
- Old accounts are no longer listed in WebFinger as aliases; this was breaking spec
## Fixed
- Issue preventing fetching anything from IPv6-only instances
- Issue allowing post content to leak via opengraph tags despite :estrict\_unauthenticated being set
- Move activities no longer operate on stale user data
- Missing definitions in our JSON-LD context
- Issue mangling newlines in code blocks for RSS/Atom feeds
- static\_fe squeezing non-square avatars and emoji
- Issue leading to properly JSON-LD compacted emoji reactions being rejected
- We now use a standard-compliant Accept header when fetching ActivityPub objects
- /api/pleroma/notification\_settings was rejecting body parameters;
this also broke changing this setting via akkoma-fe
- Issue leading to Mastodon bot accounts being rejected
- Scope misdetection of remote posts resulting from not recognising
JSON-LD-compacted forms of public scope; affected e.g. federation with bovine
## Removed
- ActivityPub Client-To-Server write API endpoints have been disabled;
read endpoints are planned to be removed next release unless a clear need is demonstrated
## 2024.03
## Added
- CLI tasks best-effort checking for past abuse of the recent spoofing exploit
- new `:mrf_steal_emoji, :download_unknown_size` option; defaults to `false`
## Changed
- `Pleroma.Upload, :base_url` now MUST be configured explicitly if used;
use of the same domain as the instance is **strongly** discouraged
- `:media_proxy, :base_url` now MUST be configured explicitly if used;
use of the same domain as the instance is **strongly** discouraged
- StealEmoji:
- now uses the pack.json format;
existing users must migrate with an out-of-band script (check release notes)
- only steals shortcodes recognised as valid
- URLs of stolen emoji is no longer predictable
- The `Dedupe` upload filter is now always active;
`AnonymizeFilenames` is again opt-in
- received AP data is sanity checked before we attempt to parse it as a user
- Uploads, emoji and media proxy now restrict Content-Type headers to a safe subset
- Akkoma will no longer fetch and parse objects hosted on the same domain
## Fixed
- Critical security issue allowing Akkoma to be used as a vector for
(depending on configuration) impersonation of other users or creation
of bogus users and posts on the upload domain
- Critical security issue letting Akkoma fall for the above impersonation
payloads due to lack of strict id checking
- Critical security issue allowing domains redirect to to pose as the initial domain
(e.g. with media proxy's fallback redirects)
- refetched objects can no longer attribute themselves to third-party actors
(this had no externally visible effect since actor info is read from the Create activity)
- our litepub JSON-LD schema is now served with the correct content type
- remote APNG attachments are now recognised as images
## Upgrade Notes
- As mentioned in "Changed", `Pleroma.Upload, :base_url` **MUST** be configured. Uploads will fail without it.
- Akkoma will refuse to start if this is not set.
- Same with media proxy.
## 2024.02
## Added
- Full compatibility with Erlang OTP26
- handling of GET /api/v1/preferences

2
Procfile Normal file
View File

@ -0,0 +1,2 @@
web: mix phx.server
release: mix ecto.migrate

View File

@ -1,21 +1,16 @@
# Akkoma backend security handling
# Pleroma backend security policy
## Supported versions
Currently, Pleroma offers bugfixes and security patches only for the latest minor release.
| Version | Support
|---------| --------
| 2.2 | Bugfixes and security patches
## Reporting a vulnerability
Please send an email (preferably encrypted) or
a DM via our IRC to one of the following people:
| Forgejo nick | IRC nick | Email | GPG |
| ------------ | ------------- | ------------- | --------------------------------------- |
| floatinghost | FloatingGhost | *see GPG key* | https://coffee-and-dreams.uk/pubkey.asc |
Please use confidential issues (tick the "This issue is confidential and should only be visible to team members with at least Reporter access." box when submitting) at our [bugtracker](https://git.pleroma.social/pleroma/pleroma/-/issues/new) for reporting vulnerabilities.
## Announcements
New releases and security issues are announced at
[meta.akkoma.dev](https://meta.akkoma.dev/c/releases) and
[@akkoma@ihatebeinga.live](https://ihatebeinga.live/akkoma).
Both also offer RSS feeds
([meta](https://meta.akkoma.dev/c/releases/7.rss),
[fedi](https://ihatebeinga.live/users/akkoma.rss))
so you can keep an eye on it without any accounts.
New releases are announced at [pleroma.social](https://pleroma.social/announcements/). All security releases are tagged with ["Security"](https://pleroma.social/announcements/tags/security/). You can be notified of them by subscribing to an Atom feed at <https://pleroma.social/announcements/tags/security/feed.xml>.

View File

@ -61,12 +61,11 @@ config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.
# Upload configuration
config :pleroma, Pleroma.Upload,
uploader: Pleroma.Uploaders.Local,
filters: [],
filters: [Pleroma.Upload.Filter.Dedupe],
link_name: false,
proxy_remote: false,
filename_display_max_length: 30,
base_url: nil,
allowed_mime_types: ["image", "audio", "video"]
base_url: nil
config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads"
@ -149,38 +148,18 @@ config :logger, :ex_syslogger,
format: "$metadata[$level] $message",
metadata: [:request_id]
# ———————————————————————————————————————————————————————————————
# W A R N I N G
# ———————————————————————————————————————————————————————————————
#
# Whenever adding a privileged new custom type for e.g.
# ActivityPub objects, ALWAYS map their extension back
# to "application/octet-stream".
# Else files served by us can automatically end up with
# those privileged types causing severe security hazards.
# (We need those mappings so Phoenix can assoiate its format
# (the "extension") to incoming requests of those MIME types)
#
# ———————————————————————————————————————————————————————————————
config :mime, :types, %{
"application/xml" => ["xml"],
"application/xrd+xml" => ["xrd+xml"],
"application/jrd+json" => ["jrd+json"],
"application/activity+json" => ["activity+json"],
"application/ld+json" => ["activity+json"],
# Can be removed when bumping MIME past 2.0.5
# see https://akkoma.dev/AkkomaGang/akkoma/issues/657
"image/apng" => ["apng"]
"application/ld+json" => ["activity+json"]
}
config :mime, :extensions, %{
"xrd+xml" => "text/plain",
"jrd+json" => "text/plain",
"activity+json" => "text/plain"
"activity+json" => "application/activity+json"
}
# ———————————————————————————————————————————————————————————————
config :tesla, :adapter, {Tesla.Adapter.Finch, name: MyFinch}
# Configures http settings, upstream proxy etc.
@ -427,6 +406,8 @@ config :pleroma, :mrf_object_age,
threshold: 604_800,
actions: [:delist, :strip_followers]
config :pleroma, :mrf_follow_bot, follower_nickname: nil
config :pleroma, :mrf_reject_newly_created_account_notes, age: 86_400
config :pleroma, :rich_media,

View File

@ -100,22 +100,9 @@ config :pleroma, :config_description, [
label: "Base URL",
type: :string,
description:
"Base URL for the uploads. Required if you use a CDN or host attachments under a different domain - it is HIGHLY recommended that you **do not** set this to be the same as the domain akkoma is hosted on.",
"Base URL for the uploads. Required if you use a CDN or host attachments under a different domain.",
suggestions: [
"https://media.akkoma.dev/media/"
]
},
%{
key: :allowed_mime_types,
label: "Allowed MIME types",
type: {:list, :string},
description:
"List of MIME (main) types uploads are allowed to identify themselves with. Other types may still be uploaded, but will identify as a generic binary to clients. WARNING: Loosening this over the defaults can lead to security issues. Removing types is safe, but only add to the list if you are sure you know what you are doing.",
suggestions: [
"image",
"audio",
"video",
"font"
"https://cdn-host.com"
]
},
%{
@ -222,26 +209,6 @@ config :pleroma, :config_description, [
}
]
},
%{
group: :pleroma,
key: Pleroma.Upload.Filter.Exiftool.StripMetadata,
type: :group,
description: "Strip specified metadata from image uploads",
children: [
%{
key: :purge,
description: "Metadata fields or groups to strip",
type: {:list, :string},
suggestions: ["all", "CommonIFD0"]
},
%{
key: :preserve,
description: "Metadata fields or groups to preserve (takes precedence over stripping)",
type: {:list, :string},
suggestions: ["ColorSpaces", "Orientation"]
}
]
},
%{
group: :pleroma,
key: Pleroma.Emails.Mailer,

View File

@ -22,12 +22,9 @@ config :logger, :console,
config :pleroma, :auth, oauth_consumer_strategies: []
config :pleroma, Pleroma.Upload,
base_url: "http://localhost:4001/media/",
filters: [],
link_name: false
config :pleroma, :media_proxy, base_url: "http://localhost:4001"
config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads"
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Test, enabled: true

7
coveralls.json Normal file
View File

@ -0,0 +1,7 @@
{
"skip_files": [
"test/support",
"lib/mix/tasks/pleroma/benchmark.ex",
"lib/credo/check/consistency/file_location.ex"
]
}

View File

@ -46,7 +46,7 @@ services:
volumes:
- .:/opt/akkoma
# Copy this into docker-compose.override.yml and uncomment there if you want to use a reverse proxy
# Uncomment the following if you want to use a reverse proxy
#proxy:
# image: caddy:2-alpine
# restart: unless-stopped

View File

@ -37,8 +37,7 @@ If any of the options are left unspecified, you will be prompted interactively.
- `--static-dir <path>` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)
- `--listen-ip <ip>` - the ip the app should listen to, defaults to 127.0.0.1
- `--listen-port <port>` - the port the app should listen to, defaults to 4000
- `--strip-uploads-metadata <Y|N>` - use ExifTool to strip uploads of metadata when possible
- `--read-uploads-description <Y|N>` - use ExifTool to read image descriptions from uploads
- `--strip-uploads <Y|N>` - use ExifTool to strip uploads of sensitive location data
- `--anonymize-uploads <Y|N>` - randomize uploaded filenames
- `--dedupe-uploads <Y|N>` - store files based on their hash to reduce data storage requirements if duplicates are uploaded with different filenames
- `--skip-release-env` - skip generation the release environment file

View File

@ -1,56 +0,0 @@
# Security-related tasks
{! administration/CLI_tasks/general_cli_task_info.include !}
!!! danger
Many of these tasks were written in response to a patched exploit.
It is recommended to run those very soon after installing its respective security update.
Over time with db migrations they might become less accurate or be removed altogether.
If you never ran an affected version, theres no point in running them.
## Spoofed AcitivityPub objects exploit (2024-03, fixed in 3.11.1)
### Search for uploaded spoofing payloads
Scans local uploads for spoofing payloads.
If the instance is not using the local uploader it was not affected.
Attachments wil be scanned anyway in case local uploader was used in the past.
!!! note
This cannot reliably detect payloads attached to deleted posts.
=== "OTP"
```sh
./bin/pleroma_ctl security spoof-uploaded
```
=== "From Source"
```sh
mix pleroma.security spoof-uploaded
```
### Search for counterfeit posts in database
Scans all notes in the database for signs of being spoofed.
!!! note
Spoofs targeting local accounts can be detected rather reliably
(with some restrictions documented in the tasks logs).
Counterfeit posts from remote users cannot. A best-effort attempt is made, but
a thorough attacker can avoid this and it may yield a small amount of false positives.
Should you find counterfeit posts of local users, let other admins know so they can delete the too.
=== "OTP"
```sh
./bin/pleroma_ctl security spoof-inserted
```
=== "From Source"
```sh
mix pleroma.security spoof-inserted
```

View File

@ -104,60 +104,31 @@ To add configuration to your config file, you can copy it from the base config.
## Message rewrite facility
### :mrf
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
* `transparency_obfuscate_domains`: Show domains with `*` in the middle, to censor them if needed. For example, `ridingho.me` will show as `rid*****.me`
* `policies`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
* `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesnt modify activities (default).
* `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesnt makes sense to use in production.
* `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled delections.
(See [`:mrf_activity_expiration`](#mrf_activity_expiration))
* `Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy`: Drops follow requests from followbots. Users can still allow bots to follow them by first following the bot.
* `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
* `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certains instances (See [`:mrf_simple`](#mrf_simple)).
* `Pleroma.Web.ActivityPub.MRF.TagPolicy`: Applies policies to individual users based on tags, which can be set using pleroma-fe/admin-fe/any other app that supports Pleroma Admin API. For example it allows marking posts from individual users nsfw (sensitive).
* `Pleroma.Web.ActivityPub.MRF.SubchainPolicy`: Selectively runs other MRF policies when messages match (See [`:mrf_subchain`](#mrf_subchain)).
* `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See [`:mrf_rejectnonpublic`](#mrf_rejectnonpublic)).
* `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:.
* `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines.
* `Pleroma.Web.ActivityPub.MRF.HellthreadPolicy`: Blocks messages with too many mentions.
(See [`mrf_hellthread`](#mrf_hellthread))
* `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)).
* `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
* `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.
* `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (See [`:mrf_mention`](#mrf_mention)).
* `Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy`: Drops local activities which have no actual content.
(e.g. no attachments and only consists of mentions)
* `Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy`: Strips content placeholders from posts
(such as the dot from mastodon)
* `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)).
* `Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy`: Rejects posts of users the server only recently learned about for a while. Great to block spam accounts. (See [`:mrf_reject_newly_created_account_notes`](#mrf_reject_newly_created_account_notes))
* `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See [`:mrf_rejectnonpublic`](#mrf_rejectnonpublic)).
* `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certains instances (See [`:mrf_simple`](#mrf_simple)).
* `Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy`: Steals all eligible emoji encountered in posts from remote instances
(See [`:mrf_steal_emoji`](#mrf_steal_emoji))
* `Pleroma.Web.ActivityPub.MRF.SubchainPolicy`: Selectively runs other MRF policies when messages match (See [`:mrf_subchain`](#mrf_subchain)).
* `Pleroma.Web.ActivityPub.MRF.TagPolicy`: Applies policies to individual users based on tags, which can be set using pleroma-fe/admin-fe/any other app that supports Pleroma Admin API. For example it allows marking posts from individual users nsfw (sensitive).
* `Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy`: Drops all posts except from users specified in a list.
(See [`:mrf_user_allowlist`](#mrf_user_allowlist))
* `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)).
Additionally the following MRFs will *always* be aplied and cannot be disabled:
* `Pleroma.Web.ActivityPub.MRF.DirectMessageDisabledPolicy`: Strips users limiting who can send them DMs from the recipients of non-eligible DMs
* `Pleroma.Web.ActivityPub.MRF.HashtagPolicy`: Depending on a posts hashtags it can be rejected, get its sensitive flags force-enabled or removed from the global timeline
(See [`:mrf_hashtag`](#mrf_hashtag))
* `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Append a link to a post that quotes another post with the link to the quoted post, to ensure that software that does not understand quotes can have full context.
(See [`:mrf_inline_quote`](#mrf_inline_quote))
* `Pleroma.Web.ActivityPub.MRF.NormalizeMarkup`: Pass inbound HTML through a scrubber to make sure it doesn't have anything unusual in it.
(See [`:mrf_normalize_markup`](#mrf_normalize_markup))
* `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)).
* `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled delections.
* `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines.
* `Pleroma.Web.ActivityPub.MRF.FollowBotPolicy`: Automatically follows newly discovered users from the specified bot account. Local accounts, locked accounts, and users with "#nobot" in their bio are respected and excluded from being followed.
* `Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy`: Drops follow requests from followbots. Users can still allow bots to follow them by first following the bot.
* `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)).
* `Pleroma.Web.ActivityPub.MRF.NormalizeMarkup`: Pass inbound HTML through a scrubber to make sure it doesn't have anything unusual in it. On by default, cannot be turned off.
* `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Append a link to a post that quotes another post with the link to the quoted post, to ensure that software that does not understand quotes can have full context. On by default, cannot be turned off.
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
* `transparency_obfuscate_domains`: Show domains with `*` in the middle, to censor them if needed. For example, `ridingho.me` will show as `rid*****.me`
## Federation
### :activitypub
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
* `outgoing_blocks`: Whether to federate blocks to other instances
* `blockers_visible`: Whether a user can see the posts of users who blocked them
* `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question
* `sign_object_fetches`: Sign object fetches with HTTP signatures
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
* `max_collection_objects`: The maximum number of objects to fetch from a remote AP collection.
### MRF policies
!!! note
@ -236,9 +207,7 @@ config :pleroma, :mrf_user_allowlist, %{
#### :mrf_steal_emoji
* `hosts`: List of hosts to steal emojis from
* `rejected_shortcodes`: Regex-list of shortcodes to reject
* `size_limit`: File size limit (in bytes), checked before download if possible (and remote server honest),
otherwise or again checked before saving emoji to the disk
* `download_unknown_size`: whether to download an emoji when the remote server doesnt report its size in advance
* `size_limit`: File size limit (in bytes), checked before an emoji is saved to the disk
#### :mrf_activity_expiration
@ -254,24 +223,14 @@ Notes:
- The hashtags in the configuration do not have a leading `#`.
- This MRF Policy is always enabled, if you want to disable it you have to set empty lists
#### :mrf_reject_newly_created_account_notes
After initially encountering an user, all their posts
will be rejected for the configured time (in seconds).
Only drops posts. Follows, reposts, etc. are not affected.
* `age`: Time below which to reject (in seconds)
An example: (86400 seconds = 24 hours)
```elixir
config :pleroma, :mrf_reject_newly_created_account_notes, age: 86400
```
#### :mrf_inline_quote
* `prefix`: what prefix to prepend to quoted URLs
#### :mrf_normalize_markup
* `scrub_policy`: the scrubbing module to use (by default a built-in HTML sanitiser)
### :activitypub
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
* `outgoing_blocks`: Whether to federate blocks to other instances
* `blockers_visible`: Whether a user can see the posts of users who blocked them
* `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question
* `sign_object_fetches`: Sign object fetches with HTTP signatures
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
* `max_collection_objects`: The maximum number of objects to fetch from a remote AP collection.
## Pleroma.User
@ -398,8 +357,7 @@ This section describe PWA manifest instance-specific values. Currently this opti
## :media_proxy
* `enabled`: Enables proxying of remote media to the instances proxy
* `base_url`: The base URL to access a user-uploaded file.
Using a (sub)domain distinct from the instance endpoint is **strongly** recommended.
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
* `proxy_opts`: All options defined in `Pleroma.ReverseProxy` documentation, defaults to `[max_body_length: (25*1_048_576)]`.
* `whitelist`: List of hosts with scheme to bypass the mediaproxy (e.g. `https://example.com`)
* `invalidation`: options for remove media from cache after delete object:
@ -600,9 +558,8 @@ the source code is here: [kocaptcha](https://github.com/koto-bank/kocaptcha). Th
* `uploader`: Which one of the [uploaders](#uploaders) to use.
* `filters`: List of [upload filters](#upload-filters) to use.
* `link_name`: When enabled Akkoma will add a `name` parameter to the url of the upload, for example `https://instance.tld/media/corndog.png?name=corndog.png`. This is needed to provide the correct filename in Content-Disposition headers
* `base_url`: The base URL to access a user-uploaded file; MUST be configured explicitly.
Using a (sub)domain distinct from the instance endpoint is **strongly** recommended. A good value might be `https://media.myakkoma.instance/media/`.
* `link_name`: When enabled Akkoma will add a `name` parameter to the url of the upload, for example `https://instance.tld/media/corndog.png?name=corndog.png`. This is needed to provide the correct filename in Content-Disposition headers when using filters like `Pleroma.Upload.Filter.Dedupe`
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to host the media files via another domain or are using a 3rd party S3 provider.
* `proxy_remote`: If you're using a remote uploader, Akkoma will proxy media requests instead of redirecting to it.
* `proxy_opts`: Proxy options, see `Pleroma.ReverseProxy` documentation.
* `filename_display_max_length`: Set max length of a filename to display. 0 = no limit. Default: 30.
@ -642,29 +599,20 @@ config :ex_aws, :s3,
### Upload filters
#### Pleroma.Upload.Filter.Dedupe
**Always** active; cannot be turned off.
Renames files to their hash and prevents duplicate files filling up the disk.
No specific configuration.
#### Pleroma.Upload.Filter.AnonymizeFilename
This filter replaces the declared filename (not the path) of an upload.
This filter replaces the filename (not the path) of an upload. For complete obfuscation, add
`Pleroma.Upload.Filter.Dedupe` before AnonymizeFilename.
* `text`: Text to replace filenames in links. If empty, `{random}.extension` will be used. You can get the original filename extension by using `{extension}`, for example `custom-file-name.{extension}`.
#### Pleroma.Upload.Filter.Exiftool.StripMetadata
#### Pleroma.Upload.Filter.Dedupe
This filter strips metadata with Exiftool leaving color profiles and orientation intact.
No specific configuration.
* `purge`: List of Exiftool tag names or tag group names to purge
* `preserve`: List of Exiftool tag names or tag group names to preserve even if they occur in the purge list
#### Pleroma.Upload.Filter.Exiftool
#### Pleroma.Upload.Filter.Exiftool.ReadDescription
This filter reads the ImageDescription and iptc:Caption-Abstract fields with Exiftool so clients can prefill the media description field.
This filter only strips the GPS and location metadata with Exiftool leaving color profiles and attributes intact.
No specific configuration.

View File

@ -17,16 +17,6 @@ This sets the Akkoma application server to only listen to the localhost interfac
This sets the `secure` flag on Akkomas session cookie. This makes sure, that the cookie is only accepted over encrypted HTTPs connections. This implicitly renames the cookie from `pleroma_key` to `__Host-pleroma-key` which enforces some restrictions. (see [cookie prefixes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Cookie_prefixes))
### `Pleroma.Upload, :uploader, :base_url`
> Recommended value: *anything on a different domain than the instance endpoint; e.g. https://media.myinstance.net/*
Uploads are user controlled and (unless youre running a true single-user
instance) should therefore not be considered trusted. But the domain is used
as a pivilege boundary e.g. by HTTP content security policy and ActivityPub.
Having uploads on the same domain enabled several past vulnerabilities
able to be exploited by malicious users.
### `:http_security`
> Recommended value: `true`

View File

@ -6,16 +6,7 @@ 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 well assume so)*
* In this subdomains server block add
* Edit your nginx config and add the following location:
```
location /proxy {
proxy_cache akkoma_media_cache;
@ -35,9 +26,9 @@ config :pleroma, :media_proxy,
enabled: true,
proxy_opts: [
redirect_on_failure: true
],
base_url: "https://cache.akkoma.social"
]
#base_url: "https://cache.akkoma.social"
```
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.
If you want to use a subdomain to serve the files, uncomment `base_url`, change the url and add a comma after `true` in the previous line.
* Restart nginx and Akkoma

View File

@ -25,14 +25,11 @@ Tuning the BEAM requires you provide a config file normally called [vm.args](htt
`ExecStart=/usr/bin/elixir --erl '-args_file /opt/akkoma/config/vm.args' -S /usr/bin/mix phx.server`
If using an OTP release, set the `RELEASE_VM_ARGS` environment variable to the path to the vm.args file.
Check your OS documentation to adopt a similar strategy on other platforms.
### Virtual Machine and/or few CPU cores
Disable the busy-waiting. This should generally be done if you're on a platform that does burst scheduling, like AWS, or if you're running other
services on the same machine.
Disable the busy-waiting. This should generally only be done if you're on a platform that does burst scheduling, like AWS.
**vm.args:**
@ -42,8 +39,6 @@ services on the same machine.
+sbwtdio none
```
These settings are enabled by default for OTP releases
### Dedicated Hardware
Enable more busy waiting, increase the internal maximum limit of BEAM processes and ports. You can use this if you run on dedicated hardware, but it is not necessary.

View File

@ -145,13 +145,47 @@ If you want to open your newly installed instance to the world, you should run n
doas apk add nginx
```
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
```shell
doas apk add certbot
```
and then set it up:
```shell
doas mkdir -p /var/lib/letsencrypt/
doas certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
```
If that doesnt work, make sure, that nginx is not already running. If it still doesnt work, try setting up nginx first (change ssl “on” to “off” and try again).
* Copy the example nginx configuration to the nginx folder
```shell
doas cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/conf.d/akkoma.conf
```
* Before starting nginx edit the configuration and change it to your needs. You must change change `server_name`. You can use `nano` (install with `apk add nano` if missing).
* Before starting nginx edit the configuration and change it to your needs. You must change change `server_name` and the paths to the certificates. You can use `nano` (install with `apk add nano` if missing).
```
server {
server_name your.domain;
listen 80;
...
}
server {
server_name your.domain;
listen 443 ssl http2;
...
ssl_trusted_certificate /etc/letsencrypt/live/your.domain/chain.pem;
ssl_certificate /etc/letsencrypt/live/your.domain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your.domain/privkey.pem;
...
}
```
* Enable and start nginx:
```shell
@ -159,37 +193,10 @@ doas rc-update add nginx
doas rc-service nginx start
```
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
```shell
doas apk add certbot certbot-nginx
```
and then set it up:
```shell
doas mkdir -p /var/lib/letsencrypt/
doas certbot --email <your@emailaddress> -d <yourdomain> -d <media_domain> --nginx
```
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
To automatically renew, set up a cron job like so:
```shell
# Enable the crond service
doas rc-update add crond
doas rc-service crond start
# Test that renewals work
doas certbot renew --cert-name yourinstance.tld --nginx --dry-run
# Add the renewal task to cron
echo '#!/bin/sh
certbot renew --cert-name yourinstance.tld --nginx
' | doas tee /etc/periodic/daily/renew-akkoma-cert
doas chmod +x /etc/periodic/daily/renew-akkoma-cert
doas certbot certonly --email <your@emailaddress> -d <yourdomain> --webroot -w /var/lib/letsencrypt/
```
#### OpenRC service

View File

@ -136,17 +136,16 @@ If you want to open your newly installed instance to the world, you should run n
sudo pacman -S nginx
```
* Copy the example nginx configuration:
* Create directories for available and enabled sites:
```shell
sudo cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/conf.d/akkoma.conf
sudo mkdir -p /etc/nginx/sites-{available,enabled}
```
* Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths)
* Enable and start nginx:
* Append the following line at the end of the `http` block in `/etc/nginx/nginx.conf`:
```shell
sudo systemctl enable --now nginx.service
```Nginx
include sites-enabled/*;
```
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
@ -159,18 +158,32 @@ and then set it up:
```shell
sudo mkdir -p /var/lib/letsencrypt/
sudo certbot --email <your@emailaddress> -d <yourdomain> -d <media_domain> --nginx
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
```
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
If that doesnt work, make sure, that nginx is not already running. If it still doesnt work, try setting up nginx first (change ssl “on” to “off” and try again).
To make sure renewals work, enable the appropriate systemd timer:
---
* Copy the example nginx configuration and activate it:
```shell
sudo systemctl enable --now certbot-renew.timer
sudo cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/sites-available/akkoma.nginx
sudo ln -s /etc/nginx/sites-available/akkoma.nginx /etc/nginx/sites-enabled/akkoma.nginx
```
Certificate renewal should be handled automatically by Certbot from now on.
* Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths)
* Enable and start nginx:
```shell
sudo systemctl enable --now nginx.service
```
If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
```shell
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --webroot -w /var/lib/letsencrypt/
```
#### Other webserver/proxies

View File

@ -155,6 +155,23 @@ If you want to open your newly installed instance to the world, you should run n
sudo apt install nginx
```
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
```shell
sudo apt install certbot
```
and then set it up:
```shell
sudo mkdir -p /var/lib/letsencrypt/
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
```
If that doesnt work, make sure, that nginx is not already running. If it still doesnt work, try setting up nginx first (change ssl “on” to “off” and try again).
---
* Copy the example nginx configuration and activate it:
```shell
@ -169,23 +186,12 @@ sudo ln -s /etc/nginx/sites-available/akkoma.nginx /etc/nginx/sites-enabled/akko
sudo systemctl enable --now nginx.service
```
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
```shell
sudo apt install certbot python3-certbot-nginx
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --webroot -w /var/lib/letsencrypt/
```
and then set it up:
```shell
sudo mkdir -p /var/lib/letsencrypt/
sudo certbot --email <your@emailaddress> -d <yourdomain> -d <media_domain> --nginx
```
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
Certificate renewal should be handled automatically by Certbot from now on.
#### Other webserver/proxies
You can find example configurations for them in `/opt/akkoma/installation/`.

View File

@ -125,26 +125,7 @@ cp docker-resources/Caddyfile.example docker-resources/Caddyfile
Then edit the TLD in your caddyfile to the domain you're serving on.
Copy the commented out `caddy` section in `docker-compose.yml` into a new file called `docker-compose.override.yml` like so:
```yaml
version: "3.7"
services:
proxy:
image: caddy:2-alpine
restart: unless-stopped
links:
- akkoma
ports: [
"443:443",
"80:80"
]
volumes:
- ./docker-resources/Caddyfile:/etc/caddy/Caddyfile
- ./caddy-data:/data
- ./caddy-config:/config
```
Uncomment the `caddy` section in the docker compose file,
then run `docker compose up -d` again.
#### Running a reverse proxy on the host
@ -174,12 +155,6 @@ git pull
docker compose restart akkoma db
```
### Modifying the Docker services
If you want to modify the services defined in the docker compose file, you can
create a new file called `docker-compose.override.yml`. There you can add any
overrides or additional services without worrying about git conflicts when a
new release comes out.
#### Further reading
{! installation/further_reading.include !}

View File

@ -135,6 +135,23 @@ If you want to open your newly installed instance to the world, you should run n
sudo dnf install nginx
```
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
```shell
sudo dnf install certbot
```
and then set it up:
```shell
sudo mkdir -p /var/lib/letsencrypt/
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
```
If that doesnt work, make sure, that nginx is not already running. If it still doesnt work, try setting up nginx first (change ssl “on” to “off” and try again).
---
* Copy the example nginx configuration and activate it:
```shell
@ -148,23 +165,12 @@ sudo cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/conf.d/akkoma.con
sudo systemctl enable --now nginx.service
```
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
```shell
sudo dnf install certbot python3-certbot-nginx
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --webroot -w /var/lib/letsencrypt/
```
and then set it up:
```shell
sudo certbot --email <your@emailaddress> -d <yourdomain> -d <media_domain> --nginx
```
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
Certificate renewal should be handled automatically by Certbot from now on.
#### Other webserver/proxies
You can find example configurations for them in `/opt/akkoma/installation/`.

View File

@ -1,8 +1,8 @@
## Required dependencies
* PostgreSQL 9.6+
* Elixir 1.14+ (currently tested up to 1.16)
* Erlang OTP 25+ (currently tested up to OTP26)
* Elixir 1.14+
* Erlang OTP 25+
* git
* file / libmagic
* gcc (clang might also work)

View File

@ -201,6 +201,25 @@ Assuming you want to open your newly installed federated social network to, well
include sites-enabled/*;
```
* Setup your SSL cert, using your method of choice or certbot. If using certbot, install it if you haven't already:
```shell
# emerge --ask app-crypt/certbot app-crypt/certbot-nginx
```
and then set it up:
```shell
# mkdir -p /var/lib/letsencrypt/
# certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
```
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. If that doesnt work, make sure, that nginx is not already running. If it still doesnt work, try setting up nginx first (change ssl “on” to “off” and try again). Often the answer to issues with certbot is to use the `--nginx` flag once you have nginx up and running.
If you are using any additional subdomains, such as for a media proxy, you can re-run the same command with the subdomain in question. When it comes time to renew later, you will not need to run multiple times for each domain, one renew will handle it.
---
* Copy the example nginx configuration and activate it:
```shell
@ -218,24 +237,9 @@ Pay special attention to the line that begins with `ssl_ecdh_curve`. It is stong
```shell
# rc-update add nginx default
# rc-service nginx start
# /etc/init.d/nginx start
```
* Setup your SSL cert, using your method of choice or certbot. If using certbot, install it if you haven't already:
```shell
# emerge --ask app-crypt/certbot app-crypt/certbot-nginx
```
and then set it up:
```shell
# mkdir -p /var/lib/letsencrypt/
# certbot --email <your@emailaddress> -d <yourdomain> -d <media_domain> --nginx
```
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
If you are using certbot, it is HIGHLY recommend you set up a cron job that renews your certificate, and that you install the suggested `certbot-nginx` plugin. If you don't do these things, you only have yourself to blame when your instance breaks suddenly because you forgot about it.
First, ensure that the command you will be installing into your crontab works.

View File

@ -21,33 +21,6 @@ fork of Akkoma - luckily this isn't very hard.
You'll need to update the backend, then possibly the frontend, depending
on your setup.
## Backup diverging features
As time goes on Akkoma and Pleroma added or removed different features
and reorganised the database in a different way. If you want to be able to
migrate back to Pleroma without losing any affected data, youll want to
make a backup before starting the migration.
If you're not interested in migrating back, skip this section
*(although it might be a good idea to temporarily keep a full DB backup
just in case something unexpected happens during migration)*
As of 2024-02 you will want to keep a backup of:
- the entire `chats` and `chat_message_references` tables
The following columns are not deleted by a migration to Akkoma, but a migration
back to Pleroma or future Akkoma upgrades might affect them, so perhaps back them up as well:
- the `birthday` of users and their `show_birthday` setting
- the `expires_at` key of in the `user_relationships` table
*(used by temporary mutes)*
The way cached instance metadata is stored differs, but since those
will be refetched and updated anyway, theres no need for a backup.
Best check all newer migrations unique to Akkoma/Pleroma
to get an up-to-date picture of what needs to be kept.
## From Source
If you're running the source Akkoma install, you'll need to set the
@ -61,7 +34,16 @@ git pull -r
# to run "git merge stable" instead (or develop if you want)
```
And compile as usual.
### WARNING - Migrating from Pleroma Develop
If you are on pleroma develop, and have updated since 2022-08, you may have issues with database migrations.
Please roll back the given migrations:
```bash
MIX_ENV=prod mix ecto.rollback --migrations-path priv/repo/optional_migrations/pleroma_develop_rollbacks -n5
```
Then compile, migrate and restart as usual.
## From OTP
@ -71,44 +53,15 @@ This will just be setting the update URL - find your flavour from the [mapping o
export FLAVOUR=[the flavour you found above]
./bin/pleroma_ctl update --zip-url https://akkoma-updates.s3-website.fr-par.scw.cloud/stable/akkoma-$FLAVOUR.zip
./bin/pleroma_ctl migrate
```
When updating in the future, you can just use
Then restart. When updating in the future, you canjust use
```bash
./bin/pleroma_ctl update --branch stable
```
## Database Migrations
### WARNING - Migrating from Pleroma past 2022-08
If you are on Pleroma stable >= 2.5.0 or Pleroma develop, and
have updated since 2022-08, you may have issues with database migrations.
Please first roll back the given migrations:
=== "OTP"
```bash
./bin/pleroma_ctl rollback --migrations-path priv/repo/optional_migrations/pleroma_develop_rollbacks -n5
```
=== "From Source"
```bash
MIX_ENV=prod mix ecto.rollback --migrations-path priv/repo/optional_migrations/pleroma_develop_rollbacks -n5
```
### Applying Akkoma Database Migrations
Just run
=== "OTP"
```bash
./bin/pleroma_ctl migrate
```
=== "From Source"
```bash
MIX_ENV=prod mix ecto.migrate
```
## Frontend changes
Akkoma comes with a few frontend changes as well as backend ones,
@ -177,4 +130,3 @@ MIX_ENV=prod mix ecto.rollback --to 20210416051708
```
Then switch back to Pleroma for updates (similar to how was done to migrate to Akkoma), and remove the front-ends. The front-ends are installed in the `frontends` folder in the [static directory](../configuration/static_dir.md). Once you are back to Pleroma, you will need to run the database migrations again. See the Pleroma documentation for this.
After this use your previous backups to restore data from diverging features.

View File

@ -14,7 +14,7 @@ Note: the packages are not required with the current default settings of Akkoma.
`ImageMagick` is a set of tools to create, edit, compose, or convert bitmap images.
It is required for the following Akkoma features:
* `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Pleroma.Upload/filters` in `config/config.exs`)
* `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Plaroma.Upload/filters` in `config/config.exs`)
* Media preview proxy for still images (related config: `media_preview_proxy/enabled` in `config/config.exs`)
## `ffmpeg`
@ -29,5 +29,4 @@ It is required for the following Akkoma features:
`exiftool` is media files metadata reader/writer.
It is required for the following Akkoma features:
* `Pleroma.Upload.Filters.Exiftool.StripMetadata` upload filter (related config: `Pleroma.Upload/filters` in `config/config.exs`)
* `Pleroma.Upload.Filters.Exiftool.ReadDescription` upload filter (related config: `Pleroma.Upload/filters` in `config/config.exs`)
* `Pleroma.Upload.Filters.Exiftool` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`)

View File

@ -9,7 +9,7 @@ This guide covers a installation using an OTP release. To install Akkoma from so
* For installing OTP releases on RedHat-based distros like Fedora and Centos Stream, please follow [this guide](./otp_redhat_en.md) instead.
* A (sub)domain pointed to the machine
You will be running commands as root. If you aren't root already, please elevate your priviledges by executing `sudo -i`/`su`.
You will be running commands as root. If you aren't root already, please elevate your priviledges by executing `sudo su`/`su`.
While in theory OTP releases are possbile to install on any compatible machine, for the sake of simplicity this guide focuses only on Debian/Ubuntu and Alpine.
@ -176,6 +176,11 @@ su akkoma -s $SHELL -lc "./bin/pleroma stop"
### Setting up nginx and getting Let's Encrypt SSL certificaties
#### Get a Let's Encrypt certificate
```sh
certbot certonly --standalone --preferred-challenges http -d yourinstance.tld
```
#### Copy Akkoma nginx configuration to the nginx folder
The location of nginx configs is dependent on the distro
@ -204,14 +209,6 @@ $EDITOR path-to-nginx-config
# Verify that the config is valid
nginx -t
```
#### Get a Let's Encrypt certificate
```sh
certbot --nginx -d yourinstance.tld -d media.yourinstance.tld
```
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
#### Start nginx
=== "Alpine"
@ -255,19 +252,32 @@ If everything worked, you should see Akkoma-FE when visiting your domain. If tha
## Post installation
### Setting up auto-renew of the Let's Encrypt certificate
```sh
# Create the directory for webroot challenges
mkdir -p /var/lib/letsencrypt
# Uncomment the webroot method
$EDITOR path-to-nginx-config
# Verify that the config is valid
nginx -t
```
=== "Alpine"
```
# Restart nginx
rc-service nginx restart
# Start the cron daemon and make it start on boot
rc-service crond start
rc-update add crond
# Ensure the webroot menthod and post hook is working
certbot renew --cert-name yourinstance.tld --nginx --dry-run
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'rc-service nginx reload'
# Add it to the daily cron
echo '#!/bin/sh
certbot renew --cert-name yourinstance.tld --nginx
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "rc-service nginx reload"
' > /etc/periodic/daily/renew-akkoma-cert
chmod +x /etc/periodic/daily/renew-akkoma-cert
@ -276,7 +286,22 @@ If everything worked, you should see Akkoma-FE when visiting your domain. If tha
```
=== "Debian/Ubuntu"
This should be automatically enabled with the `certbot-renew.timer` systemd unit.
```
# Restart nginx
systemctl restart nginx
# Ensure the webroot menthod and post hook is working
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'systemctl reload nginx'
# Add it to the daily cron
echo '#!/bin/sh
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "systemctl reload nginx"
' > /etc/cron.daily/renew-akkoma-cert
chmod +x /etc/cron.daily/renew-akkoma-cert
# If everything worked the output should contain /etc/cron.daily/renew-akkoma-cert
run-parts --test /etc/cron.daily
```
## Create your first user and set as admin
```sh

View File

@ -82,7 +82,6 @@ Other than things bundled in the OTP release Akkoma depends on:
* PostgreSQL (also utilizes extensions in postgresql-contrib)
* nginx (could be swapped with another reverse proxy but this guide covers only it)
* certbot (for Let's Encrypt certificates, could be swapped with another ACME client, but this guide covers only it)
* If you are using certbot, also install the `python3-certbot-nginx` package for the nginx plugin
* libmagic/file
First, update your system, if not already done:
@ -170,6 +169,12 @@ sudo -Hu akkoma ./bin/pleroma stop
### Setting up nginx and getting Let's Encrypt SSL certificaties
#### Get a Let's Encrypt certificate
```shell
certbot certonly --standalone --preferred-challenges http -d yourinstance.tld
```
#### Copy Akkoma nginx configuration to the nginx folder
```shell
@ -190,15 +195,8 @@ sudo nginx -t
sudo systemctl start nginx
```
#### Get a Let's Encrypt certificate
At this point if you open your (sub)domain in a browser you should see a 502 error, that's because Akkoma is not started yet.
```shell
sudo certbot --email <your@emailaddress> -d <yourdomain> -d <media_domain> --nginx
```
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
If you're successful with obtaining the certificates, opening your (sub)domain in a browser will result in a 502 error, since Akkoma hasn't been started yet.
### Setting up a system service
@ -241,11 +239,19 @@ sudo nginx -t
# Restart nginx
sudo systemctl restart nginx
# Test that renewals work properly
sudo certbot renew --cert-name yourinstance.tld --nginx --dry-run
# Ensure the webroot menthod and post hook is working
sudo certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'systemctl reload nginx'
# Add it to the daily cron
echo '#!/bin/sh
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "systemctl reload nginx"
' > /etc/cron.daily/renew-akkoma-cert
sudo chmod +x /etc/cron.daily/renew-akkoma-cert
# If everything worked the output should contain /etc/cron.daily/renew-akkoma-cert
sudo run-parts --test /etc/cron.daily
```
Assuming the commands were run successfully, certbot should be able to renew your certificates automatically via the `certbot-renew.timer` systemd unit.
## Create your first user and set as admin
```shell

2
elixir_buildpack.config Normal file
View File

@ -0,0 +1,2 @@
elixir_version=1.14.3
erlang_version=25.3

View File

@ -1,9 +1,12 @@
# default nginx site config for Akkoma
#
# See the documentation at docs.akkoma.dev for your particular distro/OS for
# installation instructions.
# Simple installation instructions:
# 1. Install your TLS certificate, possibly using Let's Encrypt.
# 2. Replace 'example.tld' with your instance's domain wherever it appears.
# 3. Copy this file to /etc/nginx/sites-available/ and then add a symlink to it
# in /etc/nginx/sites-enabled/ and run 'nginx -s reload' or restart nginx.
proxy_cache_path /tmp/akkoma-media-cache levels=1:2 keys_zone=akkoma_media_cache:10m max_size=1g
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;
# this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
@ -12,19 +15,25 @@ upstream phoenix {
server 127.0.0.1:4000 max_fails=5 fail_timeout=60s;
}
# If you are setting up TLS certificates without certbot, uncomment the
# following to enable HTTP -> HTTPS redirects. Certbot users don't need to do
# this as it will automatically do this for you.
# server {
# server_name example.tld media.example.tld;
#
# listen 80;
# listen [::]:80;
#
# location / {
# return 301 https://$server_name$request_uri;
# }
# }
server {
server_name example.tld;
listen 80;
listen [::]:80;
# Uncomment this if you need to use the 'webroot' method with certbot. Make sure
# that the directory exists and that it is accessible by the webserver. If you followed
# the guide, you already ran 'mkdir -p /var/lib/letsencrypt' to create the folder.
# You may need to load this file with the ssl server block commented out, run certbot
# to get the certificate, and then uncomment it.
#
# location ~ /\.well-known/acme-challenge {
# root /var/lib/letsencrypt/;
# }
location / {
return 301 https://$server_name$request_uri;
}
}
# Enable SSL session caching for improved performance
ssl_session_cache shared:ssl_session_cache:10m;
@ -32,29 +41,22 @@ ssl_session_cache shared:ssl_session_cache:10m;
server {
server_name example.tld;
# Once certbot is set up, this will automatically be updated to listen to
# port 443 with TLS alongside a redirect from plaintext HTTP.
listen 80;
listen [::]:80;
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
# If you are not using Certbot, comment out the above and uncomment/edit the following
# listen 443 ssl http2;
# listen [::]:443 ssl http2;
# ssl_session_timeout 1d;
# ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
# ssl_session_tickets off;
#
# ssl_trusted_certificate /etc/letsencrypt/live/example.tld/chain.pem;
# ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem;
#
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
# ssl_prefer_server_ciphers off;
# ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
# ssl_stapling on;
# ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.tld/chain.pem;
ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_prefer_server_ciphers off;
ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
ssl_stapling on;
ssl_stapling_verify on;
gzip_vary on;
gzip_proxied any;
@ -73,43 +75,9 @@ server {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location ~ ^/(media|proxy) {
return 404;
}
location / {
proxy_pass http://phoenix;
}
}
# Upload and MediaProxy Subdomain
# (see main domain setup for more details)
server {
server_name media.example.tld;
# Same as above, will be updated to HTTPS once certbot is set up.
listen 80;
listen [::]:80;
# If you are not using certbot, comment the above and copy all the ssl
# stuff from above into here.
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;
# the nginx default is 1m, not enough for large media uploads
client_max_body_size 16m;
ignore_invalid_headers off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location ~ ^/(media|proxy) {
proxy_cache akkoma_media_cache;
@ -123,8 +91,4 @@ server {
chunked_transfer_encoding on;
proxy_pass http://phoenix;
}
location / {
return 404;
}
}

View File

@ -20,7 +20,6 @@ defmodule Mix.Tasks.Pleroma.Instance do
output: :string,
output_psql: :string,
domain: :string,
media_url: :string,
instance_name: :string,
admin_email: :string,
notify_email: :string,
@ -35,9 +34,9 @@ defmodule Mix.Tasks.Pleroma.Instance do
static_dir: :string,
listen_ip: :string,
listen_port: :string,
strip_uploads_metadata: :string,
read_uploads_description: :string,
anonymize_uploads: :string
strip_uploads: :string,
anonymize_uploads: :string,
dedupe_uploads: :string
],
aliases: [
o: :output,
@ -65,14 +64,6 @@ defmodule Mix.Tasks.Pleroma.Instance do
":"
) ++ [443]
media_url =
get_option(
options,
:media_url,
"What base url will uploads use? (e.g https://media.example.com/media)\n" <>
" Generally this should NOT use the same domain as the instance "
)
name =
get_option(
options,
@ -170,38 +161,21 @@ defmodule Mix.Tasks.Pleroma.Instance do
)
|> Path.expand()
{strip_uploads_metadata_message, strip_uploads_metadata_default} =
{strip_uploads_message, strip_uploads_default} =
if Pleroma.Utils.command_available?("exiftool") do
{"Do you want to strip metadata from uploaded images? This requires exiftool, it was detected as installed. (y/n)",
{"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as installed. (y/n)",
"y"}
else
{"Do you want to strip metadata from uploaded images? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
{"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
"n"}
end
strip_uploads_metadata =
strip_uploads =
get_option(
options,
:strip_uploads_metadata,
strip_uploads_metadata_message,
strip_uploads_metadata_default
) === "y"
{read_uploads_description_message, read_uploads_description_default} =
if Pleroma.Utils.command_available?("exiftool") do
{"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as installed. (y/n)",
"y"}
else
{"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
"n"}
end
read_uploads_description =
get_option(
options,
:read_uploads_description,
read_uploads_description_message,
read_uploads_description_default
:strip_uploads,
strip_uploads_message,
strip_uploads_default
) === "y"
anonymize_uploads =
@ -212,6 +186,14 @@ defmodule Mix.Tasks.Pleroma.Instance do
"n"
) === "y"
dedupe_uploads =
get_option(
options,
:dedupe_uploads,
"Do you want to deduplicate uploaded files? (y/n)",
"n"
) === "y"
Config.put([:instance, :static_dir], static_dir)
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
@ -225,7 +207,6 @@ defmodule Mix.Tasks.Pleroma.Instance do
EEx.eval_file(
template_dir <> "/sample_config.eex",
domain: domain,
media_url: media_url,
port: port,
email: email,
notify_email: notify_email,
@ -248,9 +229,9 @@ defmodule Mix.Tasks.Pleroma.Instance do
listen_port: listen_port,
upload_filters:
upload_filters(%{
strip_metadata: strip_uploads_metadata,
read_description: read_uploads_description,
anonymize: anonymize_uploads
strip: strip_uploads,
anonymize: anonymize_uploads,
dedupe: dedupe_uploads
})
)
@ -324,20 +305,11 @@ defmodule Mix.Tasks.Pleroma.Instance do
end
defp upload_filters(filters) when is_map(filters) do
enabled_filters = []
enabled_filters =
if filters.read_description do
enabled_filters ++ [Pleroma.Upload.Filter.Exiftool.ReadDescription]
if filters.strip do
[Pleroma.Upload.Filter.Exiftool]
else
enabled_filters
end
enabled_filters =
if filters.strip_metadata do
enabled_filters ++ [Pleroma.Upload.Filter.Exiftool.StripMetadata]
else
enabled_filters
[]
end
enabled_filters =
@ -347,6 +319,13 @@ defmodule Mix.Tasks.Pleroma.Instance do
enabled_filters
end
enabled_filters =
if filters.dedupe do
enabled_filters ++ [Pleroma.Upload.Filter.Dedupe]
else
enabled_filters
end
enabled_filters
end
end

View File

@ -1,330 +0,0 @@
# Akkoma: Magically expressive social media
# Copyright © 2024 Akkoma Authors <https://akkoma.dev/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Security do
use Mix.Task
import Ecto.Query
import Mix.Pleroma
alias Pleroma.Config
require Logger
@shortdoc """
Security-related tasks, like e.g. checking for signs past exploits were abused.
"""
# Constants etc
defp local_id_prefix(), do: Pleroma.Web.Endpoint.url() <> "/"
defp local_id_pattern(), do: local_id_prefix() <> "%"
@activity_exts ["activity+json", "activity%2Bjson"]
defp activity_ext_url_patterns() do
for e <- @activity_exts do
for suf <- ["", "?%"] do
# Escape literal % for use in SQL patterns
ee = String.replace(e, "%", "\\%")
"%.#{ee}#{suf}"
end
end
|> List.flatten()
end
# Search for malicious uploads exploiting the lack of Content-Type sanitisation from before 2024-03
def run(["spoof-uploaded"]) do
Logger.put_process_level(self(), :notice)
start_pleroma()
IO.puts("""
+------------------------+
| SPOOF SEARCH UPLOADS |
+------------------------+
Checking if any uploads are using privileged types.
NOTE if attachment deletion is enabled, payloads used
in the past may no longer exist.
""")
do_spoof_uploaded()
end
# Fuzzy search for potentially counterfeit activities in the database resulting from the same exploit
def run(["spoof-inserted"]) do
Logger.put_process_level(self(), :notice)
start_pleroma()
IO.puts("""
+----------------------+
| SPOOF SEARCH NOTES |
+----------------------+
Starting fuzzy search for counterfeit activities.
NOTE this can not guarantee detecting all counterfeits
and may yield a small percentage of false positives.
""")
do_spoof_inserted()
end
# +-----------------------------+
# | S P O O F - U P L O A D E D |
# +-----------------------------+
defp do_spoof_uploaded() do
files =
case Config.get!([Pleroma.Upload, :uploader]) do
Pleroma.Uploaders.Local ->
uploads_search_spoofs_local_dir(Config.get!([Pleroma.Uploaders.Local, :uploads]))
_ ->
IO.puts("""
NOTE:
Not using local uploader; thus not affected by this exploit.
It's impossible to check for files, but in case local uploader was used before
or to check if anyone futilely attempted a spoof, notes will still be scanned.
""")
[]
end
emoji = uploads_search_spoofs_local_dir(Config.get!([:instance, :static_dir]))
post_attachs = uploads_search_spoofs_notes()
not_orphaned_urls =
post_attachs
|> Enum.map(fn {_u, _a, url} -> url end)
|> MapSet.new()
orphaned_attachs = upload_search_orphaned_attachments(not_orphaned_urls)
IO.puts("\nSearch concluded; here are the results:")
pretty_print_list_with_title(emoji, "Emoji")
pretty_print_list_with_title(files, "Uploaded Files")
pretty_print_list_with_title(post_attachs, "(Not Deleted) Post Attachments")
pretty_print_list_with_title(orphaned_attachs, "Orphaned Uploads")
IO.puts("""
In total found
#{length(emoji)} emoji
#{length(files)} uploads
#{length(post_attachs)} not deleted posts
#{length(orphaned_attachs)} orphaned attachments
""")
end
defp uploads_search_spoofs_local_dir(dir) do
local_dir = String.replace_suffix(dir, "/", "")
IO.puts("Searching for suspicious files in #{local_dir}...")
glob_ext = "{" <> Enum.join(@activity_exts, ",") <> "}"
Path.wildcard(local_dir <> "/**/*." <> glob_ext, match_dot: true)
|> Enum.map(fn path ->
String.replace_prefix(path, local_dir <> "/", "")
end)
|> Enum.sort()
end
defp uploads_search_spoofs_notes() do
IO.puts("Now querying DB for posts with spoofing attachments. This might take a while...")
patterns = [local_id_pattern() | activity_ext_url_patterns()]
# if jsonb_array_elemsts in FROM can be used with normal Ecto functions, idk how
"""
SELECT DISTINCT a.data->>'actor', a.id, url->>'href'
FROM public.objects AS o JOIN public.activities AS a
ON o.data->>'id' = a.data->>'object',
jsonb_array_elements(o.data->'attachment') AS attachs,
jsonb_array_elements(attachs->'url') AS url
WHERE o.data->>'type' = 'Note' AND
o.data->>'id' LIKE $1::text AND (
url->>'href' LIKE $2::text OR
url->>'href' LIKE $3::text OR
url->>'href' LIKE $4::text OR
url->>'href' LIKE $5::text
)
ORDER BY a.data->>'actor', a.id, url->>'href';
"""
|> Pleroma.Repo.query!(patterns, timeout: :infinity)
|> map_raw_id_apid_tuple()
end
defp upload_search_orphaned_attachments(not_orphaned_urls) do
IO.puts("""
Now querying DB for orphaned spoofing attachment (i.e. their post was deleted,
but if :cleanup_attachments was not enabled traces remain in the database)
This might take a bit...
""")
patterns = activity_ext_url_patterns()
"""
SELECT DISTINCT attach.id, url->>'href'
FROM public.objects AS attach,
jsonb_array_elements(attach.data->'url') AS url
WHERE (attach.data->>'type' = 'Image' OR
attach.data->>'type' = 'Document')
AND (
url->>'href' LIKE $1::text OR
url->>'href' LIKE $2::text OR
url->>'href' LIKE $3::text OR
url->>'href' LIKE $4::text
)
ORDER BY attach.id, url->>'href';
"""
|> Pleroma.Repo.query!(patterns, timeout: :infinity)
|> then(fn res -> Enum.map(res.rows, fn [id, url] -> {id, url} end) end)
|> Enum.filter(fn {_, url} -> !(url in not_orphaned_urls) end)
end
# +-----------------------------+
# | S P O O F - I N S E R T E D |
# +-----------------------------+
defp do_spoof_inserted() do
IO.puts("""
Searching for local posts whose Create activity has no ActivityPub id...
This is a pretty good indicator, but only for spoofs of local actors
and only if the spoofing happened after around late 2021.
""")
idless_create =
search_local_notes_without_create_id()
|> Enum.sort()
IO.puts("Done.\n")
IO.puts("""
Now trying to weed out other poorly hidden spoofs.
This can't detect all and may have some false positives.
""")
likely_spoofed_posts_set = MapSet.new(idless_create)
sus_pattern_posts =
search_sus_notes_by_id_patterns()
|> Enum.filter(fn r -> !(r in likely_spoofed_posts_set) end)
IO.puts("Done.\n")
IO.puts("""
Finally, searching for spoofed, local user accounts.
(It's impossible to detect spoofed remote users)
""")
spoofed_users = search_bogus_local_users()
pretty_print_list_with_title(sus_pattern_posts, "Maybe Spoofed Posts")
pretty_print_list_with_title(idless_create, "Likely Spoofed Posts")
pretty_print_list_with_title(spoofed_users, "Spoofed local user accounts")
IO.puts("""
In total found:
#{length(spoofed_users)} bogus users
#{length(idless_create)} likely spoofed posts
#{length(sus_pattern_posts)} maybe spoofed posts
""")
end
defp search_local_notes_without_create_id() do
Pleroma.Object
|> where([o], fragment("?->>'id' LIKE ?", o.data, ^local_id_pattern()))
|> join(:inner, [o], a in Pleroma.Activity,
on: fragment("?->>'object' = ?->>'id'", a.data, o.data)
)
|> where([o, a], fragment("NOT (? \\? 'id') OR ?->>'id' IS NULL", a.data, a.data))
|> select([o, a], {a.id, fragment("?->>'id'", o.data)})
|> order_by([o, a], a.id)
|> Pleroma.Repo.all(timeout: :infinity)
end
defp search_sus_notes_by_id_patterns() do
[ep1, ep2, ep3, ep4] = activity_ext_url_patterns()
Pleroma.Object
|> where(
[o],
# for local objects we know exactly how a genuine id looks like
# (though a thorough attacker can emulate this)
# for remote posts, use some best-effort patterns
fragment(
"""
(?->>'id' LIKE ? AND ?->>'id' NOT SIMILAR TO
? || 'objects/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}')
""",
o.data,
^local_id_pattern(),
o.data,
^local_id_prefix()
) or
fragment("?->>'id' LIKE ?", o.data, "%/emoji/%") or
fragment("?->>'id' LIKE ?", o.data, "%/media/%") or
fragment("?->>'id' LIKE ?", o.data, "%/proxy/%") or
fragment("?->>'id' LIKE ?", o.data, ^ep1) or
fragment("?->>'id' LIKE ?", o.data, ^ep2) or
fragment("?->>'id' LIKE ?", o.data, ^ep3) or
fragment("?->>'id' LIKE ?", o.data, ^ep4)
)
|> join(:inner, [o], a in Pleroma.Activity,
on: fragment("?->>'object' = ?->>'id'", a.data, o.data)
)
|> select([o, a], {a.id, fragment("?->>'id'", o.data)})
|> order_by([o, a], a.id)
|> Pleroma.Repo.all(timeout: :infinity)
end
defp search_bogus_local_users() do
Pleroma.User.Query.build(%{})
|> where([u], u.local == false and like(u.ap_id, ^local_id_pattern()))
|> order_by([u], u.ap_id)
|> select([u], u.ap_id)
|> Pleroma.Repo.all(timeout: :infinity)
end
# +-----------------------------------+
# | module-specific utility functions |
# +-----------------------------------+
defp pretty_print_list_with_title(list, title) do
title_len = String.length(title)
title_underline = String.duplicate("=", title_len)
IO.puts(title)
IO.puts(title_underline)
pretty_print_list(list)
end
defp pretty_print_list([]), do: IO.puts("")
defp pretty_print_list([{a, o} | rest])
when (is_binary(a) or is_number(a)) and is_binary(o) do
IO.puts(" {#{a}, #{o}}")
pretty_print_list(rest)
end
defp pretty_print_list([{u, a, o} | rest])
when is_binary(a) and is_binary(u) and is_binary(o) do
IO.puts(" {#{u}, #{a}, #{o}}")
pretty_print_list(rest)
end
defp pretty_print_list([e | rest]) when is_binary(e) do
IO.puts(" #{e}")
pretty_print_list(rest)
end
defp pretty_print_list([e | rest]), do: pretty_print_list([inspect(e) | rest])
defp map_raw_id_apid_tuple(res) do
user_prefix = local_id_prefix() <> "users/"
Enum.map(res.rows, fn
[uid, aid, oid] ->
{
String.replace_prefix(uid, user_prefix, ""),
FlakeId.to_string(aid),
oid
}
end)
end
end

View File

@ -26,15 +26,6 @@ defmodule Pleroma.Activity.Pruner do
|> Repo.delete_all(timeout: :infinity)
end
def prune_updates do
before_time = cutoff()
from(a in Activity,
where: fragment("?->>'type' = ?", a.data, "Update") and a.inserted_at < ^before_time
)
|> Repo.delete_all(timeout: :infinity)
end
def prune_removes do
before_time = cutoff()

View File

@ -288,7 +288,6 @@ defmodule Pleroma.Application do
|> Config.get([])
|> Pleroma.HTTP.AdapterHelper.add_pool_size(pool_size)
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy_pool(proxy)
|> Pleroma.HTTP.AdapterHelper.ensure_ipv6()
|> Keyword.put(:name, MyFinch)
[{Finch, config}]

View File

@ -164,8 +164,7 @@ defmodule Pleroma.ApplicationRequirements do
defp check_system_commands!(:ok) do
filter_commands_statuses = [
check_filter(Pleroma.Upload.Filter.Exiftool.StripMetadata, "exiftool"),
check_filter(Pleroma.Upload.Filter.Exiftool.ReadDescription, "exiftool"),
check_filter(Pleroma.Upload.Filter.Exiftool, "exiftool"),
check_filter(Pleroma.Upload.Filter.Mogrify, "mogrify"),
check_filter(Pleroma.Upload.Filter.Mogrifun, "mogrify"),
check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "mogrify"),

View File

@ -68,10 +68,7 @@ defmodule Akkoma.Collections.Fetcher do
items
end
else
{:error, :not_found} ->
items
{:error, :forbidden} ->
{:error, {"Object has been deleted", _, _}} ->
items
{:error, error} ->

View File

@ -22,43 +22,6 @@ defmodule Pleroma.Config.DeprecationWarnings do
"\n* `config :pleroma, :instance, :quarantined_instances` is now covered by `:pleroma, :mrf_simple, :reject`"}
]
def check_exiftool_filter do
filters = Config.get([Pleroma.Upload]) |> Keyword.get(:filters, [])
if Pleroma.Upload.Filter.Exiftool in filters do
Logger.warning("""
!!!DEPRECATION WARNING!!!
Your config is using Exiftool as a filter instead of Exiftool.StripMetadata. This should work for now, but you are advised to change to the new configuration to prevent possible issues later:
```
config :pleroma, Pleroma.Upload,
filters: [Pleroma.Upload.Filter.Exiftool]
```
Is now
```
config :pleroma, Pleroma.Upload,
filters: [Pleroma.Upload.Filter.Exiftool.StripMetadata]
```
""")
new_config =
filters
|> Enum.map(fn
Pleroma.Upload.Filter.Exiftool -> Pleroma.Upload.Filter.Exiftool.StripMetadata
filter -> filter
end)
Config.put([Pleroma.Upload, :filters], new_config)
:error
else
:ok
end
end
def check_simple_policy_tuples do
has_strings =
Config.get([:mrf_simple])
@ -219,10 +182,7 @@ defmodule Pleroma.Config.DeprecationWarnings do
check_quarantined_instances_tuples(),
check_transparency_exclusions_tuples(),
check_simple_policy_tuples(),
check_http_adapter(),
check_uploader_base_url_set(),
check_uploader_base_url_is_not_base_domain(),
check_exiftool_filter()
check_http_adapter()
]
|> Enum.reduce(:ok, fn
:ok, :ok -> :ok
@ -377,54 +337,4 @@ defmodule Pleroma.Config.DeprecationWarnings do
:ok
end
end
def check_uploader_base_url_set() do
uses_local_uploader? = Config.get([Pleroma.Upload, :uploader]) == Pleroma.Uploaders.Local
base_url = Pleroma.Config.get([Pleroma.Upload, :base_url])
if base_url || !uses_local_uploader? do
:ok
else
Logger.error("""
!!!WARNING!!!
Your config does not specify a base_url for uploads!
Please make the following change:\n
\n* `config :pleroma, Pleroma.Upload, base_url: "https://example.com/media/`
\n
\nPlease note that it is HEAVILY recommended to use a subdomain to host user-uploaded media!
""")
# This is a hard exit - the uploader will not work without a base_url
raise ArgumentError, message: "No base_url set for uploads - please set one in your config!"
end
end
def check_uploader_base_url_is_not_base_domain() do
uses_local_uploader? = Config.get([Pleroma.Upload, :uploader]) == Pleroma.Uploaders.Local
uploader_host =
[Pleroma.Upload, :base_url]
|> Pleroma.Config.get()
|> URI.parse()
|> Map.get(:host)
akkoma_host =
[Pleroma.Web.Endpoint, :url]
|> Pleroma.Config.get()
|> Keyword.get(:host)
if uploader_host == akkoma_host && uses_local_uploader? do
Logger.error("""
!!!WARNING!!!
Your Akkoma Host and your Upload base_url's host are the same!
This can potentially be insecure!
It is HIGHLY recommended that you migrate your media uploads
to a subdomain at your earliest convenience
""")
end
# This isn't actually an error condition, just a warning
:ok
end
end

View File

@ -55,61 +55,12 @@ defmodule Pleroma.Emails.Mailer do
@doc false
def validate_dependency do
parse_config([], defaults: false)
parse_config([])
|> Keyword.get(:adapter)
|> Swoosh.Mailer.validate_dependency()
end
defp ensure_charlist(input) do
case input do
i when is_binary(i) -> String.to_charlist(input)
i when is_list(i) -> i
end
end
defp default_config(adapter, conf, opts)
defp default_config(_, _, defaults: false) do
[]
end
defp default_config(Swoosh.Adapters.SMTP, conf, _) do
# gen_smtp and Erlang's tls defaults are very barebones, if nothing is set.
# Add sane defaults for our usecase to make config less painful for admins
relay = ensure_charlist(Keyword.get(conf, :relay))
ssl_disabled = Keyword.get(conf, :ssl) === false
os_cacerts = :public_key.cacerts_get()
common_tls_opts = [
cacerts: os_cacerts,
versions: [:"tlsv1.2", :"tlsv1.3"],
verify: :verify_peer,
# some versions have supposedly issues verifying wildcard certs without this
server_name_indication: relay,
# the default of 10 is too restrictive
depth: 32
]
[
auth: :always,
no_mx_lookups: false,
# Direct SSL/TLS
# (if ssl was explicitly disabled, we must not pass TLS options to the socket)
ssl: true,
sockopts: if(ssl_disabled, do: [], else: common_tls_opts),
# STARTTLS upgrade (can't be set to :always when already using direct TLS)
tls: :if_available,
tls_options: common_tls_opts
]
end
defp default_config(_, _, _), do: []
defp parse_config(config, opts \\ []) do
conf = Swoosh.Mailer.parse_config(@otp_app, __MODULE__, @mailer_config, config)
adapter = Keyword.get(conf, :adapter)
default_config(adapter, conf, opts)
|> Keyword.merge(conf)
defp parse_config(config) do
Swoosh.Mailer.parse_config(@otp_app, __MODULE__, @mailer_config, config)
end
end

View File

@ -26,37 +26,12 @@ defmodule Pleroma.Emoji.Pack do
alias Pleroma.Emoji.Pack
alias Pleroma.Utils
# Invalid/Malicious names are supposed to be filtered out before path joining,
# but there are many entrypoints to affected functions so as the code changes
# we might accidentally let an unsanitised name slip through.
# To make sure, use the below which crash the process otherwise.
# ALWAYS use this when constructing paths from external name!
# (name meaning it must be only a single path component)
defp path_join_name_safe(dir, name) do
if to_string(name) != Path.basename(name) or name in ["..", ".", ""] do
raise "Invalid or malicious pack name: #{name}"
else
Path.join(dir, name)
end
end
# ALWAYS use this to join external paths
# (which are allowed to have several components)
defp path_join_safe(dir, path) do
{:ok, safe_path} = Path.safe_relative(path)
Path.join(dir, safe_path)
end
@spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values}
def create(name) do
with :ok <- validate_not_empty([name]),
dir <- path_join_name_safe(emoji_path(), name),
dir <- Path.join(emoji_path(), name),
:ok <- File.mkdir(dir) do
save_pack(%__MODULE__{
path: dir,
pack_file: Path.join(dir, "pack.json")
})
save_pack(%__MODULE__{pack_file: Path.join(dir, "pack.json")})
end
end
@ -90,7 +65,7 @@ defmodule Pleroma.Emoji.Pack do
{:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values}
def delete(name) do
with :ok <- validate_not_empty([name]),
pack_path <- path_join_name_safe(emoji_path(), name) do
pack_path <- Path.join(emoji_path(), name) do
File.rm_rf(pack_path)
end
end
@ -114,7 +89,7 @@ defmodule Pleroma.Emoji.Pack do
end)
end
@spec add_file(t(), String.t(), Path.t(), Plug.Upload.t() | binary()) ::
@spec add_file(t(), String.t(), Path.t(), Plug.Upload.t()) ::
{:ok, t()}
| {:error, File.posix() | atom()}
def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do
@ -132,7 +107,7 @@ defmodule Pleroma.Emoji.Pack do
Enum.map_reduce(emojies, pack, fn item, emoji_pack ->
emoji_file = %Plug.Upload{
filename: item[:filename],
path: path_join_safe(tmp_dir, item[:path])
path: Path.join(tmp_dir, item[:path])
}
{:ok, updated_pack} =
@ -162,14 +137,6 @@ defmodule Pleroma.Emoji.Pack do
end
def add_file(%Pack{} = pack, shortcode, filename, %Plug.Upload{} = file) do
try_add_file(pack, shortcode, filename, file)
end
def add_file(%Pack{} = pack, shortcode, filename, filedata) when is_binary(filedata) do
try_add_file(pack, shortcode, filename, filedata)
end
defp try_add_file(%Pack{} = pack, shortcode, filename, file) do
with :ok <- validate_not_empty([shortcode, filename]),
:ok <- validate_emoji_not_exists(shortcode),
{:ok, updated_pack} <- do_add_file(pack, shortcode, filename, file) do
@ -222,7 +189,6 @@ defmodule Pleroma.Emoji.Pack do
{:ok, results} <- File.ls(emoji_path) do
names =
results
# items come from File.ls, thus safe
|> Enum.map(&Path.join(emoji_path, &1))
|> Enum.reject(fn path ->
File.dir?(path) and File.exists?(Path.join(path, "pack.json"))
@ -321,8 +287,8 @@ defmodule Pleroma.Emoji.Pack do
@spec load_pack(String.t()) :: {:ok, t()} | {:error, :file.posix()}
def load_pack(name) do
pack_dir = path_join_name_safe(emoji_path(), name)
pack_file = Path.join(pack_dir, "pack.json")
name = Path.basename(name)
pack_file = Path.join([emoji_path(), name, "pack.json"])
with {:ok, _} <- File.stat(pack_file),
{:ok, pack_data} <- File.read(pack_file) do
@ -446,13 +412,7 @@ defmodule Pleroma.Emoji.Pack do
end
defp create_archive_and_cache(pack, hash) do
files = [
~c"pack.json"
| Enum.map(pack.files, fn {_, file} ->
{:ok, file} = Path.safe_relative(file)
to_charlist(file)
end)
]
files = [~c"pack.json" | Enum.map(pack.files, fn {_, file} -> to_charlist(file) end)]
{:ok, {_, result}} =
:zip.zip(~c"#{pack.name}.zip", files, [:memory, cwd: to_charlist(pack.path)])
@ -514,7 +474,7 @@ defmodule Pleroma.Emoji.Pack do
end
defp save_file(%Plug.Upload{path: upload_path}, pack, filename) do
file_path = path_join_safe(pack.path, filename)
file_path = Path.join(pack.path, filename)
create_subdirs(file_path)
with {:ok, _} <- File.copy(upload_path, file_path) do
@ -522,12 +482,6 @@ defmodule Pleroma.Emoji.Pack do
end
end
defp save_file(file_data, pack, filename) when is_binary(file_data) do
file_path = path_join_safe(pack.path, filename)
create_subdirs(file_path)
File.write(file_path, file_data, [:binary])
end
defp put_emoji(pack, shortcode, filename) do
files = Map.put(pack.files, shortcode, filename)
%{pack | files: files, files_count: length(Map.keys(files))}
@ -539,8 +493,8 @@ defmodule Pleroma.Emoji.Pack do
end
defp rename_file(pack, filename, new_filename) do
old_path = path_join_safe(pack.path, filename)
new_path = path_join_safe(pack.path, new_filename)
old_path = Path.join(pack.path, filename)
new_path = Path.join(pack.path, new_filename)
create_subdirs(new_path)
with :ok <- File.rename(old_path, new_path) do
@ -558,7 +512,7 @@ defmodule Pleroma.Emoji.Pack do
defp remove_file(pack, shortcode) do
with {:ok, filename} <- get_filename(pack, shortcode),
emoji <- path_join_safe(pack.path, filename),
emoji <- Path.join(pack.path, filename),
:ok <- File.rm(emoji) do
remove_dir_if_empty(emoji, filename)
end
@ -576,7 +530,7 @@ defmodule Pleroma.Emoji.Pack do
defp get_filename(pack, shortcode) do
with %{^shortcode => filename} when is_binary(filename) <- pack.files,
file_path <- path_join_safe(pack.path, filename),
file_path <- Path.join(pack.path, filename),
{:ok, _} <- File.stat(file_path) do
{:ok, filename}
else
@ -614,7 +568,7 @@ defmodule Pleroma.Emoji.Pack do
end
defp copy_as(remote_pack, local_name) do
path = path_join_name_safe(emoji_path(), local_name)
path = Path.join(emoji_path(), local_name)
%__MODULE__{
name: local_name,

View File

@ -65,15 +65,6 @@ defmodule Pleroma.HTTP.AdapterHelper do
|> put_in([:pools, :default, :size], pool_size)
end
def ensure_ipv6(opts) do
# Default transport opts already enable IPv6, so just ensure they're loaded
opts
|> maybe_add_pools()
|> maybe_add_default_pool()
|> maybe_add_conn_opts()
|> maybe_add_transport_opts()
end
defp maybe_add_pools(opts) do
if Keyword.has_key?(opts, :pools) do
opts
@ -105,15 +96,11 @@ defmodule Pleroma.HTTP.AdapterHelper do
defp maybe_add_transport_opts(opts) do
transport_opts = get_in(opts, [:pools, :default, :conn_opts, :transport_opts])
opts =
unless is_nil(transport_opts) do
opts
else
put_in(opts, [:pools, :default, :conn_opts, :transport_opts], [])
end
# IPv6 is disabled and IPv4 enabled by default; ensure we can use both
put_in(opts, [:pools, :default, :conn_opts, :transport_opts, :inet6], true)
unless is_nil(transport_opts) do
opts
else
put_in(opts, [:pools, :default, :conn_opts, :transport_opts], [])
end
end
@doc """

View File

@ -178,10 +178,7 @@ defmodule Pleroma.Object do
ap_id
Keyword.get(options, :fetch) ->
case Fetcher.fetch_object_from_id(ap_id, options) do
{:ok, object} -> object
_ -> nil
end
Fetcher.fetch_object_from_id!(ap_id, options)
true ->
get_cached_by_ap_id(ap_id)

View File

@ -11,9 +11,6 @@ defmodule Pleroma.Object.Containment do
Object containment is an important step in validating remote objects to prevent
spoofing, therefore removal of object containment functions is NOT recommended.
"""
alias Pleroma.Web.ActivityPub.Transmogrifier
def get_actor(%{"actor" => actor}) when is_binary(actor) do
actor
end
@ -50,31 +47,6 @@ defmodule Pleroma.Object.Containment do
defp compare_uris(%URI{host: host} = _id_uri, %URI{host: host} = _other_uri), do: :ok
defp compare_uris(_id_uri, _other_uri), do: :error
defp compare_uris_exact(uri, uri), do: :ok
defp compare_uris_exact(%URI{} = id, %URI{} = other),
do: compare_uris_exact(URI.to_string(id), URI.to_string(other))
defp compare_uris_exact(id_uri, other_uri)
when is_binary(id_uri) and is_binary(other_uri) do
norm_id = String.replace_suffix(id_uri, "/", "")
norm_other = String.replace_suffix(other_uri, "/", "")
if norm_id == norm_other, do: :ok, else: :error
end
@doc """
Checks whether an URL to fetch from is from the local server.
We never want to fetch from ourselves; if its not in the database
it cant be authentic and must be a counterfeit.
"""
def contain_local_fetch(id) do
case compare_uris(URI.parse(id), Pleroma.Web.Endpoint.struct_url()) do
:ok -> :error
_ -> :ok
end
end
@doc """
Checks that an imported AP object's actor matches the host it came from.
"""
@ -90,31 +62,8 @@ defmodule Pleroma.Object.Containment do
def contain_origin(id, %{"attributedTo" => actor} = params),
do: contain_origin(id, Map.put(params, "actor", actor))
def contain_origin(_id, _data), do: :ok
def contain_origin(_id, _data), do: :error
@doc """
Check whether the fetch URL (after redirects) exactly (sans tralining slash) matches either
the canonical ActivityPub id or the objects url field (for display URLs from *key and Mastodon)
Since this is meant to be used for fetches, anonymous or transient objects are not accepted here.
"""
def contain_id_to_fetch(url, %{"id" => id} = data) when is_binary(id) do
with {:id, :error} <- {:id, compare_uris_exact(id, url)},
# "url" can be a "Link" object and this is checked before full normalisation
display_url <- Transmogrifier.fix_url(data)["url"],
true <- display_url != nil do
compare_uris_exact(display_url, url)
else
{:id, :ok} -> :ok
_ -> :error
end
end
def contain_id_to_fetch(_url, _data), do: :error
@doc """
Check whether the object id is from the same host as another id
"""
def contain_origin_from_id(id, %{"id" => other_id} = _params) when is_binary(other_id) do
id_uri = URI.parse(id)
other_uri = URI.parse(other_id)
@ -136,12 +85,4 @@ defmodule Pleroma.Object.Containment do
do: contain_origin(id, object)
def contain_child(_), do: :ok
@doc "Checks whether two URIs belong to the same domain"
def same_origin(id1, id2) do
uri1 = URI.parse(id1)
uri2 = URI.parse(id2)
compare_uris(uri1, uri2)
end
end

View File

@ -18,16 +18,6 @@ defmodule Pleroma.Object.Fetcher do
require Logger
require Pleroma.Constants
@moduledoc """
This module deals with correctly fetching Acitivity Pub objects in a safe way.
The core function is `fetch_and_contain_remote_object_from_id/1` which performs
the actual fetch and common safety and authenticity checks. Other `fetch_*`
function use the former and perform some additional tasks
"""
@mix_env Mix.env()
defp touch_changeset(changeset) do
updated_at =
NaiveDateTime.utc_now()
@ -113,78 +103,54 @@ defmodule Pleroma.Object.Fetcher do
end
end
@doc "Assumes object already is in our database and refetches from remote to update (e.g. for polls)"
def refetch_object(%Object{data: %{"id" => id}} = object) do
with {:local, false} <- {:local, Object.local?(object)},
{:ok, new_data} <- fetch_and_contain_remote_object_from_id(id),
{:id, true} <- {:id, new_data["id"] == id},
{:ok, object} <- reinject_object(object, new_data) do
{:ok, object}
else
{:local, true} -> {:ok, object}
{:id, false} -> {:error, :id_mismatch}
e -> {:error, e}
end
end
@doc """
Fetches a new object and puts it through the processing pipeline for inbound objects
Note: will also insert a fake Create activity, since atm we internally
need everything to be traced back to a Create activity.
"""
# Note: will create a Create activity, which we need internally at the moment.
def fetch_object_from_id(id, options \\ []) do
with %URI{} = uri <- URI.parse(id),
# let's check the URI is even vaguely valid first
{:valid_uri_scheme, true} <-
{:valid_uri_scheme, uri.scheme == "http" or uri.scheme == "https"},
{:scheme, true} <- {:scheme, uri.scheme == "http" or uri.scheme == "https"},
# If we have instance restrictions, apply them here to prevent fetching from unwanted instances
{:mrf_reject_check, {:ok, nil}} <-
{:mrf_reject_check, Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_reject(uri)},
{:mrf_accept_check, {:ok, _}} <-
{:mrf_accept_check, Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_accept(uri)},
{:ok, nil} <- Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_reject(uri),
{:ok, _} <- Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_accept(uri),
{_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
{_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
{_, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
{_, nil} <- {:normalize, Object.normalize(data, fetch: false)},
params <- prepare_activity_params(data),
{_, :ok} <- {:containment, Containment.contain_origin(id, params)},
{_, {:ok, activity}} <-
{:transmogrifier, Transmogrifier.handle_incoming(params, options)},
{_, _data, %Object{} = object} <-
{:object, data, Object.normalize(activity, fetch: false)} do
{:ok, object}
else
{:allowed_depth, false} = e ->
log_fetch_error(id, e)
{:error, :allowed_depth}
{:allowed_depth, false} ->
{:error, "Max thread distance exceeded."}
{:valid_uri_scheme, _} = e ->
log_fetch_error(id, e)
{:error, :invalid_uri_scheme}
{:scheme, false} ->
{:error, "URI Scheme Invalid"}
{:mrf_reject_check, _} = e ->
log_fetch_error(id, e)
{:reject, :mrf}
{:containment, _} ->
{:error, "Object containment failed."}
{:mrf_accept_check, _} = e ->
log_fetch_error(id, e)
{:reject, :mrf}
{:transmogrifier, {:error, {:reject, e}}} ->
{:reject, e}
{:containment, reason} = e ->
log_fetch_error(id, e)
{:error, reason}
{:transmogrifier, {:reject, e}} ->
{:reject, e}
{:transmogrifier, {:error, {:reject, reason}}} = e ->
log_fetch_error(id, e)
{:reject, reason}
{:transmogrifier, {:reject, reason}} = e ->
log_fetch_error(id, e)
{:reject, reason}
{:transmogrifier, reason} = e ->
log_fetch_error(id, e)
{:error, reason}
{:transmogrifier, _} = e ->
{:error, e}
{:object, data, nil} ->
reinject_object(%Object{}, data)
@ -195,21 +161,17 @@ defmodule Pleroma.Object.Fetcher do
{:fetch_object, %Object{} = object} ->
{:ok, object}
{:fetch, {:error, reason}} = e ->
log_fetch_error(id, e)
{:error, reason}
{:fetch, {:error, error}} ->
{:error, error}
{:reject, reason} ->
{:reject, reason}
e ->
log_fetch_error(id, e)
{:error, e}
e
end
end
defp log_fetch_error(id, error) do
Logger.metadata(object: id)
Logger.error("Object rejected while fetching #{id} #{inspect(error)}")
end
defp prepare_activity_params(data) do
%{
"type" => "Create",
@ -223,6 +185,26 @@ defmodule Pleroma.Object.Fetcher do
|> Maps.put_if_present("bcc", data["bcc"])
end
def fetch_object_from_id!(id, options \\ []) do
with {:ok, object} <- fetch_object_from_id(id, options) do
object
else
{:error, %Tesla.Mock.Error{}} ->
nil
{:error, {"Object has been deleted", _id, _code}} ->
nil
{:reject, reason} ->
Logger.debug("Rejected #{id} while fetching: #{inspect(reason)}")
nil
e ->
Logger.error("Error while fetching #{id}: #{inspect(e)}")
nil
end
end
defp make_signature(id, date) do
uri = URI.parse(id)
@ -253,7 +235,6 @@ defmodule Pleroma.Object.Fetcher do
end
end
@doc "Fetches arbitrary remote object and performs basic safety and authenticity checks"
def fetch_and_contain_remote_object_from_id(id)
def fetch_and_contain_remote_object_from_id(%{"id" => id}),
@ -262,46 +243,18 @@ defmodule Pleroma.Object.Fetcher do
def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
Logger.debug("Fetching object #{id} via AP")
with {:valid_uri_scheme, true} <- {:valid_uri_scheme, String.starts_with?(id, "http")},
%URI{} = uri <- URI.parse(id),
{:mrf_reject_check, {:ok, nil}} <-
{:mrf_reject_check, Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_reject(uri)},
{:mrf_accept_check, {:ok, _}} <-
{:mrf_accept_check, Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_accept(uri)},
{:local_fetch, :ok} <- {:local_fetch, Containment.contain_local_fetch(id)},
{:ok, final_id, body} <- get_object(id),
with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
{:ok, body} <- get_object(id),
{:ok, data} <- safe_json_decode(body),
{_, :ok} <- {:strict_id, Containment.contain_id_to_fetch(final_id, data)},
{_, :ok} <- {:containment, Containment.contain_origin(final_id, data)} do
unless Instances.reachable?(final_id) do
Instances.set_reachable(final_id)
:ok <- Containment.contain_origin_from_id(id, data) do
unless Instances.reachable?(id) do
Instances.set_reachable(id)
end
{:ok, data}
else
{:strict_id, _} = e ->
log_fetch_error(id, e)
{:error, :id_mismatch}
{:mrf_reject_check, _} = e ->
log_fetch_error(id, e)
{:reject, :mrf}
{:mrf_accept_check, _} = e ->
log_fetch_error(id, e)
{:reject, :mrf}
{:valid_uri_scheme, _} = e ->
log_fetch_error(id, e)
{:error, :invalid_uri_scheme}
{:local_fetch, _} = e ->
log_fetch_error(id, e)
{:error, :local_resource}
{:containment, reason} ->
log_fetch_error(id, reason)
{:error, reason}
{:scheme, _} ->
{:error, "Unsupported URI scheme"}
{:error, e} ->
{:error, e}
@ -312,85 +265,47 @@ defmodule Pleroma.Object.Fetcher do
end
def fetch_and_contain_remote_object_from_id(_id),
do: {:error, :invalid_id}
do: {:error, "id must be a string"}
defp check_crossdomain_redirect(final_host, original_url)
# HOPEFULLY TEMPORARY
# Basically none of our Tesla mocks in tests set the (supposed to
# exist for Tesla proper) url parameter for their responses
# causing almost every fetch in test to fail otherwise
if @mix_env == :test do
defp check_crossdomain_redirect(nil, _) do
{:cross_domain_redirect, false}
end
end
defp check_crossdomain_redirect(final_host, original_url) do
{:cross_domain_redirect, final_host != URI.parse(original_url).host}
end
if @mix_env == :test do
defp get_final_id(nil, initial_url), do: initial_url
defp get_final_id("", initial_url), do: initial_url
end
defp get_final_id(final_url, _intial_url) do
final_url
end
@doc "Do NOT use; only public for use in tests"
def get_object(id) do
date = Pleroma.Signature.signed_date()
headers =
[
# The first is required by spec, the second provided as a fallback for buggy implementations
{"accept", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""},
{"accept", "application/activity+json"}
]
[{"accept", "application/activity+json"}]
|> maybe_date_fetch(date)
|> sign_fetch(id, date)
with {:ok, %{body: body, status: code, headers: headers, url: final_url}}
when code in 200..299 <-
HTTP.get(id, headers),
remote_host <-
URI.parse(final_url).host,
{:cross_domain_redirect, false} <-
check_crossdomain_redirect(remote_host, id),
{:has_content_type, {_, content_type}} <-
{:has_content_type, List.keyfind(headers, "content-type", 0)},
{:parse_content_type, {:ok, "application", subtype, type_params}} <-
{:parse_content_type, Plug.Conn.Utils.media_type(content_type)} do
final_id = get_final_id(final_url, id)
case HTTP.get(id, headers) do
{:ok, %{body: body, status: code, headers: headers}} when code in 200..299 ->
case List.keyfind(headers, "content-type", 0) do
{_, content_type} ->
case Plug.Conn.Utils.media_type(content_type) do
{:ok, "application", "activity+json", _} ->
{:ok, body}
case {subtype, type_params} do
{"activity+json", _} ->
{:ok, final_id, body}
{:ok, "application", "ld+json",
%{"profile" => "https://www.w3.org/ns/activitystreams"}} ->
{:ok, body}
{"ld+json", %{"profile" => "https://www.w3.org/ns/activitystreams"}} ->
{:ok, final_id, body}
# pixelfed sometimes (and only sometimes) responds with http instead of https
{:ok, "application", "ld+json",
%{"profile" => "http://www.w3.org/ns/activitystreams"}} ->
{:ok, body}
_ ->
{:error, {:content_type, content_type}}
end
else
{:ok, %{status: code}} when code in [401, 403] ->
{:error, :forbidden}
_ ->
{:error, {:content_type, content_type}}
end
_ ->
{:error, {:content_type, nil}}
end
{:ok, %{status: code}} when code in [404, 410] ->
{:error, :not_found}
{:error, {"Object has been deleted", id, code}}
{:error, e} ->
{:error, e}
{:has_content_type, _} ->
{:error, {:content_type, nil}}
{:parse_content_type, e} ->
{:error, {:content_type, e}}
e ->
{:error, e}
end

View File

@ -17,8 +17,6 @@ defmodule Pleroma.ReverseProxy do
@failed_request_ttl :timer.seconds(60)
@methods ~w(GET HEAD)
@allowed_mime_types Pleroma.Config.get([Pleroma.Upload, :allowed_mime_types], [])
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
def max_read_duration_default, do: @max_read_duration
@ -255,7 +253,6 @@ defmodule Pleroma.ReverseProxy do
headers
|> Enum.filter(fn {k, _} -> k in @keep_resp_headers end)
|> build_resp_cache_headers(opts)
|> sanitise_content_type()
|> build_resp_content_disposition_header(opts)
|> build_csp_headers()
|> Keyword.merge(Keyword.get(opts, :resp_headers, []))
@ -285,21 +282,6 @@ defmodule Pleroma.ReverseProxy do
end
end
defp sanitise_content_type(headers) do
original_ct = get_content_type(headers)
safe_ct =
Pleroma.Web.Plugs.Utils.get_safe_mime_type(
%{allowed_mime_types: @allowed_mime_types},
original_ct
)
[
{"content-type", safe_ct}
| Enum.filter(headers, fn {k, _v} -> k != "content-type" end)
]
end
defp build_resp_content_disposition_header(headers, opts) do
opt = Keyword.get(opts, :inline_content_types, @inline_content_types)

View File

@ -61,23 +61,12 @@ defmodule Pleroma.Upload do
width: integer(),
height: integer(),
blurhash: String.t(),
description: String.t(),
path: String.t()
}
@always_enabled_filters [Pleroma.Upload.Filter.Dedupe]
@always_enabled_filters [Pleroma.Upload.Filter.AnonymizeFilename]
defstruct [
:id,
:name,
:tempfile,
:content_type,
:width,
:height,
:blurhash,
:description,
:path
]
defstruct [:id, :name, :tempfile, :content_type, :width, :height, :blurhash, :path]
@spec store(source, options :: [option()]) :: {:ok, Map.t()} | {:error, any()}
@doc "Store a file. If using a `Plug.Upload{}` as the source, be sure to use `Majic.Plug` to ensure its content_type and filename is correct."
@ -87,7 +76,7 @@ defmodule Pleroma.Upload do
with {:ok, upload} <- prepare_upload(upload, opts),
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
description = Map.get(upload, :description) || "",
description = Map.get(opts, :description) || "",
{_, true} <-
{:description_limit,
String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},
@ -163,8 +152,7 @@ defmodule Pleroma.Upload do
id: UUID.generate(),
name: file.filename,
tempfile: file.path,
content_type: file.content_type,
description: opts.description
content_type: file.content_type
}}
end
end
@ -184,8 +172,7 @@ defmodule Pleroma.Upload do
id: UUID.generate(),
name: hash <> "." <> ext,
tempfile: tmp_path,
content_type: content_type,
description: opts.description
content_type: content_type
}}
end
end
@ -248,7 +235,7 @@ defmodule Pleroma.Upload do
case uploader do
Pleroma.Uploaders.Local ->
upload_base_url
upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/"
Pleroma.Uploaders.S3 ->
bucket = Config.get([Pleroma.Uploaders.S3, :bucket])
@ -274,7 +261,7 @@ defmodule Pleroma.Upload do
end
_ ->
public_endpoint || upload_base_url
public_endpoint || upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/"
end
end
end

View File

@ -2,42 +2,24 @@
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.Exiftool.StripMetadata do
defmodule Pleroma.Upload.Filter.Exiftool do
@moduledoc """
Tries to strip all image metadata but colorspace and orientation overwriting the file in place.
Strips GPS related EXIF tags and overwrites the file in place.
Also strips or replaces filesystem metadata e.g., timestamps.
"""
@behaviour Pleroma.Upload.Filter
alias Pleroma.Config
@purge_default ["all", "CommonIFD0"]
@preserve_default ["ColorSpaceTags", "Orientation"]
@spec filter(Pleroma.Upload.t()) :: {:ok, :noop} | {:ok, :filtered} | {:error, String.t()}
# Formats not compatible with exiftool at this time
def filter(%Pleroma.Upload{content_type: "image/heic"}), do: {:ok, :noop}
def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop}
def filter(%Pleroma.Upload{content_type: "image/svg+xml"}), do: {:ok, :noop}
def filter(%Pleroma.Upload{content_type: "image/jxl"}), do: {:ok, :noop}
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
purge_args =
Config.get([__MODULE__, :purge], @purge_default)
|> Enum.map(fn mgroup -> "-" <> mgroup <> "=" end)
preserve_args =
Config.get([__MODULE__, :preserve], @preserve_default)
|> Enum.map(fn mgroup -> "-" <> mgroup end)
|> then(fn
# If -TagsFromFile is not followed by tag selectors, it will copy most available tags
[] -> []
args -> ["-TagsFromFile", "@" | args]
end)
args = ["-ignoreMinorErrors", "-overwrite_original" | purge_args] ++ preserve_args ++ [file]
try do
case System.cmd("exiftool", args, parallelism: true) do
case System.cmd("exiftool", ["-overwrite_original", "-gps:all=", file], parallelism: true) do
{_response, 0} -> {:ok, :filtered}
{error, 1} -> {:error, error}
end

View File

@ -1,52 +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.Upload.Filter.Exiftool.ReadDescription do
@moduledoc """
Gets a valid description from the related EXIF tags and provides them in the response if no description is provided yet.
It will first check ImageDescription, when that doesn't probide a valid description, it will check iptc:Caption-Abstract.
A valid description means the fields are filled in and not too long (see `:instance, :description_limit`).
"""
@behaviour Pleroma.Upload.Filter
@spec filter(Pleroma.Upload.t()) :: {:ok, any()} | {:error, String.t()}
def filter(%Pleroma.Upload{description: description})
when is_binary(description),
do: {:ok, :noop}
def filter(%Pleroma.Upload{tempfile: file} = upload),
do: {:ok, :filtered, upload |> Map.put(:description, read_description_from_exif_data(file))}
def filter(_, _), do: {:ok, :noop}
defp read_description_from_exif_data(file) do
nil
|> read_when_empty(file, "-ImageDescription")
|> read_when_empty(file, "-iptc:Caption-Abstract")
end
defp read_when_empty(current_description, _, _) when is_binary(current_description),
do: current_description
defp read_when_empty(_, file, tag) do
try do
{tag_content, 0} =
System.cmd("exiftool", ["-b", "-s3", tag, file],
stderr_to_stdout: true,
parallelism: true
)
tag_content = String.trim(tag_content)
if tag_content != "" and
String.length(tag_content) <=
Pleroma.Config.get([:instance, :description_limit]),
do: tag_content,
else: nil
rescue
_ in ErlangError -> nil
end
end
end

View File

@ -969,16 +969,15 @@ defmodule Pleroma.User do
defp maybe_send_registration_email(_), do: {:ok, :noop}
def needs_update?(user, options \\ [])
def needs_update?(%User{local: true}, _options), do: false
def needs_update?(%User{local: false, last_refreshed_at: nil}, _options), do: true
def needs_update?(%User{local: true}), do: false
def needs_update?(%User{local: false} = user, options) do
NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >=
Keyword.get(options, :maximum_age, 86_400)
def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
def needs_update?(%User{local: false} = user) do
NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
end
def needs_update?(_, _options), do: true
def needs_update?(_), do: true
# "Locked" (self-locked) users demand explicit authorization of follow requests
@spec can_direct_follow_local(User.t(), User.t()) :: true | false
@ -1981,10 +1980,10 @@ defmodule Pleroma.User do
def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
def get_or_fetch_by_ap_id(ap_id, options \\ []) do
def get_or_fetch_by_ap_id(ap_id) do
cached_user = get_cached_by_ap_id(ap_id)
maybe_fetched_user = needs_update?(cached_user, options) && fetch_by_ap_id(ap_id)
maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
case {cached_user, maybe_fetched_user} do
{_, {:ok, %User{} = user}} ->

View File

@ -22,7 +22,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Upload
alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF
alias Pleroma.Web.ActivityPub.ObjectValidators.UserValidator
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.Streamer
alias Pleroma.Web.WebFinger
@ -1705,7 +1704,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Fetcher.fetch_and_contain_remote_object_from_id(first) do
{:ok, false}
else
{:error, _} -> {:ok, true}
{:error, {:ok, %{status: code}}} when code in [401, 403] -> {:ok, true}
{:error, _} = e -> e
e -> {:error, e}
end
end
@ -1721,7 +1722,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def fetch_and_prepare_user_from_ap_id(ap_id, additional \\ []) do
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
{:valid, {:ok, _, _}} <- {:valid, UserValidator.validate(data, [])},
{:ok, data} <- user_data_from_user_object(data, additional) do
{:ok, maybe_update_follow_information(data)}
else
@ -1730,14 +1730,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
{:error, e}
{:reject, reason} = e ->
{:error, {:reject, reason} = e} ->
Logger.debug("Rejected user #{ap_id}: #{inspect(reason)}")
{:error, e}
{:valid, reason} ->
Logger.debug("Data is not a valid user #{ap_id}: #{inspect(reason)}")
{:error, "Not a user"}
{:error, e} ->
Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
{:error, e}
@ -1838,13 +1834,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, additional) do
{:ok, _pid} = Task.start(fn -> pinned_fetch_task(data) end)
user =
if data.ap_id != ap_id do
User.get_cached_by_ap_id(data.ap_id)
else
user
end
if user do
user
|> User.remote_user_changeset(data)

View File

@ -12,7 +12,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.UserView
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
@ -38,9 +40,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
# Note: :following and :followers must be served even without authentication (as via :api)
plug(
EnsureAuthenticatedPlug
when action in [:read_inbox]
when action in [:read_inbox, :update_outbox, :whoami, :upload_media]
)
plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:upload_media])
plug(
Pleroma.Web.Plugs.Cache,
[query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
@ -156,9 +160,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
end
@doc """
GET /relay/following
"""
# GET /relay/following
def relay_following(conn, _params) do
with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
conn
@ -195,9 +197,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
end
@doc """
GET /relay/followers
"""
# GET /relay/followers
def relay_followers(conn, _params) do
with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
conn
@ -317,6 +317,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|> represent_service_actor(conn)
end
@doc "Returns the authenticated user's ActivityPub User object or a 404 Not Found if non-authenticated"
def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
conn
|> put_resp_content_type("application/activity+json")
|> put_view(UserView)
|> render("user.json", %{user: user})
end
def read_inbox(
%{assigns: %{user: %User{nickname: nickname} = user}} = conn,
%{"nickname" => nickname, "page" => page?} = params
@ -367,6 +375,105 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|> json(err)
end
defp fix_user_message(%User{ap_id: actor}, %{"type" => "Create", "object" => object} = activity)
when is_map(object) do
length =
[object["content"], object["summary"], object["name"]]
|> Enum.filter(&is_binary(&1))
|> Enum.join("")
|> String.length()
limit = Pleroma.Config.get([:instance, :limit])
if length < limit do
object =
object
|> Transmogrifier.strip_internal_fields()
|> Map.put("attributedTo", actor)
|> Map.put("actor", actor)
|> Map.put("id", Utils.generate_object_id())
{:ok, Map.put(activity, "object", object)}
else
{:error,
dgettext(
"errors",
"Character limit (%{limit} characters) exceeded, contains %{length} characters",
limit: limit,
length: length
)}
end
end
defp fix_user_message(
%User{ap_id: actor} = user,
%{"type" => "Delete", "object" => object} = activity
) do
with {_, %Object{data: object_data}} <- {:normalize, Object.normalize(object, fetch: false)},
{_, true} <- {:permission, user.is_moderator || actor == object_data["actor"]} do
{:ok, activity}
else
{:normalize, _} ->
{:error, "No such object found"}
{:permission, _} ->
{:forbidden, "You can't delete this object"}
end
end
defp fix_user_message(%User{}, activity) do
{:ok, activity}
end
def update_outbox(
%{assigns: %{user: %User{nickname: nickname, ap_id: actor} = user}} = conn,
%{"nickname" => nickname} = params
) do
params =
params
|> Map.drop(["nickname"])
|> Map.put("id", Utils.generate_activity_id())
|> Map.put("actor", actor)
with {:ok, params} <- fix_user_message(user, params),
{:ok, activity, _} <- Pipeline.common_pipeline(params, local: true),
%Activity{data: activity_data} <- Activity.normalize(activity) do
conn
|> put_status(:created)
|> put_resp_header("location", activity_data["id"])
|> json(activity_data)
else
{:forbidden, message} ->
conn
|> put_status(:forbidden)
|> json(message)
{:error, message} ->
conn
|> put_status(:bad_request)
|> json(message)
e ->
Logger.warning(fn -> "AP C2S: #{inspect(e)}" end)
conn
|> put_status(:bad_request)
|> json("Bad Request")
end
end
def update_outbox(%{assigns: %{user: %User{} = user}} = conn, %{"nickname" => nickname}) do
err =
dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
nickname: nickname,
as_nickname: user.nickname
)
conn
|> put_status(:forbidden)
|> json(err)
end
defp errors(conn, {:error, :not_found}) do
conn
|> put_status(:not_found)
@ -388,6 +495,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
conn
end
def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
with {:ok, object} <-
ActivityPub.upload(
file,
actor: User.ap_id(user),
description: Map.get(data, "description")
) do
Logger.debug(inspect(object))
conn
|> put_status(:created)
|> json(object.data)
end
end
def pinned(conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname(nickname) do
conn

View File

@ -6,29 +6,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do
@moduledoc "Force a quote line into the message content."
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.Object
defp build_inline_quote(prefix, url) do
"<span class=\"quote-inline\"><br/><br/>#{prefix}: <a href=\"#{url}\">#{url}</a></span>"
end
defp resolve_urls(quote_url) do
# Fetching here can cause infinite recursion as we run this logic on inbound objects too
# This is probably not a problem - its an exceptional corner case for a local user to quote
# a post which doesn't exist
with %Object{} = obj <- Object.normalize(quote_url, fetch: false) do
id = obj.data["id"]
url = Map.get(obj.data, "url", id)
{id, url, [id, url, quote_url]}
else
_ -> {quote_url, quote_url, [quote_url]}
end
end
defp has_inline_quote?(content, urls) do
defp has_inline_quote?(content, quote_url) do
cond do
# Does the quote URL exist in the content?
Enum.any?(urls, fn url -> content =~ url end) -> true
content =~ quote_url -> true
# Does the content already have a .quote-inline span?
content =~ "<span class=\"quote-inline\">" -> true
# No inline quote found
@ -37,22 +22,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do
end
defp filter_object(%{"quoteUri" => quote_url} = object) do
{id, preferred_url, all_urls} = resolve_urls(quote_url)
object = Map.put(object, "quoteUri", id)
content = object["content"] || ""
if has_inline_quote?(content, all_urls) do
if has_inline_quote?(content, quote_url) do
object
else
prefix = Pleroma.Config.get([:mrf_inline_quote, :prefix])
content =
if String.ends_with?(content, "</p>") do
String.trim_trailing(content, "</p>") <>
build_inline_quote(prefix, preferred_url) <> "</p>"
String.trim_trailing(content, "</p>") <> build_inline_quote(prefix, quote_url) <> "</p>"
else
content <> build_inline_quote(prefix, preferred_url)
content <> build_inline_quote(prefix, quote_url)
end
Map.put(object, "content", content)

View File

@ -6,54 +6,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
require Logger
alias Pleroma.Config
alias Pleroma.Emoji.Pack
@moduledoc "Detect new emojis by their shortcode and steals them"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
@pack_name "stolen"
# Config defaults
@size_limit 50_000
@download_unknown_size false
defp create_pack() do
with {:ok, pack} = Pack.create(@pack_name) do
Pack.save_metadata(
%{
"description" => "Collection of emoji auto-stolen from other instances",
"homepage" => Pleroma.Web.Endpoint.url(),
"can-download" => false,
"share-files" => false
},
pack
)
end
end
defp load_or_create_pack() do
case Pack.load_pack(@pack_name) do
{:ok, pack} -> {:ok, pack}
{:error, :enoent} -> create_pack()
e -> e
end
end
defp add_emoji(shortcode, extension, filedata) do
{:ok, pack} = load_or_create_pack()
# Make final path infeasible to predict to thwart certain kinds of attacks
# (48 bits is slighty more than 8 base62 chars, thus 9 chars)
salt =
:crypto.strong_rand_bytes(6)
|> :crypto.bytes_to_integer()
|> Base62.encode()
|> String.pad_leading(9, "0")
filename = shortcode <> "-" <> salt <> "." <> extension
Pack.add_file(pack, shortcode, filename, filedata)
end
defp accept_host?(host), do: host in Config.get([:mrf_steal_emoji, :hosts], [])
defp shortcode_matches?(shortcode, pattern) when is_binary(pattern) do
@ -64,69 +20,30 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
String.match?(shortcode, pattern)
end
defp reject_emoji?({shortcode, _url}, installed_emoji) do
valid_shortcode? = String.match?(shortcode, ~r/^[a-zA-Z0-9_-]+$/)
rejected_shortcode? =
[:mrf_steal_emoji, :rejected_shortcodes]
|> Config.get([])
|> Enum.any?(fn pattern -> shortcode_matches?(shortcode, pattern) end)
emoji_installed? = Enum.member?(installed_emoji, shortcode)
!valid_shortcode? or rejected_shortcode? or emoji_installed?
end
defp steal_emoji(%{} = response, {shortcode, extension}) do
case add_emoji(shortcode, extension, response.body) do
{:ok, _} ->
shortcode
e ->
Logger.warning(
"MRF.StealEmojiPolicy: Failed to add #{shortcode} as #{extension}: #{inspect(e)}"
)
nil
end
end
defp get_extension_if_safe(response) do
content_type =
:proplists.get_value("content-type", response.headers, MIME.from_path(response.url))
case content_type do
"image/" <> _ -> List.first(MIME.extensions(content_type))
_ -> nil
end
end
defp is_remote_size_within_limit?(url) do
with {:ok, %{status: status, headers: headers} = _response} when status in 200..299 <-
Pleroma.HTTP.request(:head, url, nil, [], []) do
content_length = :proplists.get_value("content-length", headers, nil)
size_limit = Config.get([:mrf_steal_emoji, :size_limit], @size_limit)
accept_unknown =
Config.get([:mrf_steal_emoji, :download_unknown_size], @download_unknown_size)
content_length <= size_limit or
(content_length == nil and accept_unknown)
else
_ -> false
end
end
defp maybe_steal_emoji({shortcode, url}) do
defp steal_emoji({shortcode, url}, emoji_dir_path) do
url = Pleroma.Web.MediaProxy.url(url)
with {:remote_size, true} <- {:remote_size, is_remote_size_within_limit?(url)},
{:ok, %{status: status} = response} when status in 200..299 <- Pleroma.HTTP.get(url) do
size_limit = Config.get([:mrf_steal_emoji, :size_limit], @size_limit)
extension = get_extension_if_safe(response)
with {:ok, %{status: status} = response} when status in 200..299 <- Pleroma.HTTP.get(url) do
size_limit = Config.get([:mrf_steal_emoji, :size_limit], 50_000)
if byte_size(response.body) <= size_limit and extension do
steal_emoji(response, {shortcode, extension})
if byte_size(response.body) <= size_limit do
extension =
url
|> URI.parse()
|> Map.get(:path)
|> Path.basename()
|> Path.extname()
file_path = Path.join(emoji_dir_path, shortcode <> (extension || ".png"))
case File.write(file_path, response.body) do
:ok ->
shortcode
e ->
Logger.warning("MRF.StealEmojiPolicy: Failed to write to #{file_path}: #{inspect(e)}")
nil
end
else
Logger.debug(
"MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{size_limit} B)"
@ -148,10 +65,26 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
if host != Pleroma.Web.Endpoint.host() and accept_host?(host) do
installed_emoji = Pleroma.Emoji.get_all() |> Enum.map(fn {k, _} -> k end)
emoji_dir_path =
Config.get(
[:mrf_steal_emoji, :path],
Path.join(Config.get([:instance, :static_dir]), "emoji/stolen")
)
File.mkdir_p(emoji_dir_path)
new_emojis =
foreign_emojis
|> Enum.reject(&reject_emoji?(&1, installed_emoji))
|> Enum.map(&maybe_steal_emoji(&1))
|> Enum.reject(fn {shortcode, _url} -> shortcode in installed_emoji end)
|> Enum.filter(fn {shortcode, _url} ->
reject_emoji? =
[:mrf_steal_emoji, :rejected_shortcodes]
|> Config.get([])
|> Enum.find(false, fn pattern -> shortcode_matches?(shortcode, pattern) end)
!reject_emoji?
end)
|> Enum.map(&steal_emoji(&1, emoji_dir_path))
|> Enum.filter(& &1)
if !Enum.empty?(new_emojis) do

View File

@ -53,13 +53,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
defp fix_url(%{"url" => url} = data) when is_bitstring(url), do: data
defp fix_url(%{"url" => url} = data) when is_map(url), do: Map.put(data, "url", url["href"])
defp fix_url(%{"url" => url} = data) when is_list(url) do
data
|> Map.put("url", List.first(url))
|> fix_url()
end
defp fix_url(data), do: data
defp fix_tag(%{"tag" => tag} = data) when is_list(tag) do

View File

@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
alias Pleroma.Emoji
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@ -53,7 +52,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
defp fix(data) do
data =
data
|> Transmogrifier.fix_tag()
|> fix_emoji_qualification()
|> CommonFixes.fix_actor()
|> CommonFixes.fix_activity_addressing()

View File

@ -1,94 +0,0 @@
# Akkoma: Magically expressive social media
# Copyright © 2024 Akkoma Authors <https://akkoma.dev/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.UserValidator do
@moduledoc """
Checks whether ActivityPub data represents a valid user
Users don't go through the same ingest pipeline like activities or other objects.
To ensure this can only match a user and no users match in the other pipeline,
this is a separate from the generic ObjectValidator.
"""
@behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
alias Pleroma.Object.Containment
alias Pleroma.Signature
require Pleroma.Constants
@impl true
def validate(object, meta)
def validate(%{"type" => type, "id" => _id} = data, meta)
when type in Pleroma.Constants.actor_types() do
with :ok <- validate_pubkey(data),
:ok <- validate_inbox(data),
:ok <- contain_collection_origin(data) do
{:ok, data, meta}
else
{:error, e} -> {:error, e}
e -> {:error, e}
end
end
def validate(_, _), do: {:error, "Not a user object"}
defp mabye_validate_owner(nil, _actor), do: :ok
defp mabye_validate_owner(actor, actor), do: :ok
defp mabye_validate_owner(_owner, _actor), do: :error
defp validate_pubkey(
%{"id" => id, "publicKey" => %{"id" => pk_id, "publicKeyPem" => _key}} = data
)
when id != nil do
with {_, {:ok, kactor}} <- {:key, Signature.key_id_to_actor_id(pk_id)},
true <- id == kactor,
:ok <- mabye_validate_owner(Map.get(data, "owner"), id) do
:ok
else
{:key, _} ->
{:error, "Unable to determine actor id from key id"}
false ->
{:error, "Key id does not relate to user id"}
_ ->
{:error, "Actor does not own its public key"}
end
end
# pubkey is optional atm
defp validate_pubkey(_data), do: :ok
defp validate_inbox(%{"id" => id, "inbox" => inbox}) do
case Containment.same_origin(id, inbox) do
:ok -> :ok
:error -> {:error, "Inbox on different doamin"}
end
end
defp validate_inbox(_), do: {:error, "No inbox"}
defp check_field_value(%{"id" => id} = _data, value) do
Containment.same_origin(id, value)
end
defp maybe_check_field(data, field) do
with val when val != nil <- data[field],
:ok <- check_field_value(data, val) do
:ok
else
nil -> :ok
_ -> {:error, "#{field} on different domain"}
end
end
defp contain_collection_origin(data) do
Enum.reduce(["followers", "following", "featured"], :ok, fn
field, :ok -> maybe_check_field(data, field)
_, error -> error
end)
end
end

View File

@ -25,8 +25,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
import Ecto.Query
require Pleroma.Constants
require Logger
require Pleroma.Constants
@doc """
Modifies an incoming AP object (mastodon format) to our internal format.
@ -58,48 +58,21 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_summary(object), do: Map.put(object, "summary", "")
defp fix_addressing_list(addrs) do
def fix_addressing_list(map, field) do
addrs = map[field]
cond do
is_list(addrs) -> Enum.filter(addrs, &is_binary/1)
is_binary(addrs) -> [addrs]
true -> []
is_list(addrs) ->
Map.put(map, field, Enum.filter(addrs, &is_binary/1))
is_binary(addrs) ->
Map.put(map, field, [addrs])
true ->
Map.put(map, field, [])
end
end
# Due to JSON-LD simply "Public" and "as:Public" are equivalent to the full URI
# but to simplify later checks we only want to deal with one reperesentation internally
defp normalise_addressing_public_list(map, all_fields)
defp normalise_addressing_public_list(%{} = map, [field | fields]) do
full_uri = Pleroma.Constants.as_public()
map =
if map[field] != nil do
new_fval =
map[field]
|> fix_addressing_list()
|> Enum.map(fn
"Public" -> full_uri
"as:Public" -> full_uri
x -> x
end)
Map.put(map, field, new_fval)
else
map
end
normalise_addressing_public_list(map, fields)
end
defp normalise_addressing_public_list(map, _) do
map
end
defp normalise_addressing_public(map) do
normalise_addressing_public_list(map, ["to", "cc", "bto", "bcc"])
end
# if directMessage flag is set to true, leave the addressing alone
def fix_explicit_addressing(%{"directMessage" => true} = object, _follower_collection),
do: object
@ -123,10 +96,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("cc", final_cc)
end
def fix_addressing_list_key(map, field) do
Map.put(map, field, fix_addressing_list(map[field]))
end
def fix_addressing(object) do
{:ok, %User{follower_address: follower_collection}} =
object
@ -134,10 +103,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> User.get_or_fetch_by_ap_id()
object
|> fix_addressing_list_key("to")
|> fix_addressing_list_key("cc")
|> fix_addressing_list_key("bto")
|> fix_addressing_list_key("bcc")
|> fix_addressing_list("to")
|> fix_addressing_list("cc")
|> fix_addressing_list("bto")
|> fix_addressing_list("bcc")
|> fix_explicit_addressing(follower_collection)
|> CommonFixes.fix_implicit_addressing(follower_collection)
end
@ -166,7 +135,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("context", replied_object.data["context"] || object["conversation"])
|> Map.drop(["conversation", "inReplyToAtomUri"])
else
_ ->
e ->
Logger.warning("Couldn't fetch reply@#{inspect(in_reply_to_id)}, error: #{inspect(e)}")
object
end
else
@ -414,28 +384,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end)
end
def handle_incoming(data, options \\ []) do
data = normalise_addressing_public(data)
data =
if data["object"] != nil do
object = normalise_addressing_public(data["object"])
Map.put(data, "object", object)
else
data
end
handle_incoming_normalised(data, options)
end
defp handle_incoming_normalised(data, options)
def handle_incoming(data, options \\ [])
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
# with nil ID.
defp handle_incoming_normalised(
%{"type" => "Flag", "object" => objects, "actor" => actor} = data,
_options
) do
def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data, _options) do
with context <- data["context"] || Utils.generate_context_id(),
content <- data["content"] || "",
%User{} = actor <- User.get_cached_by_ap_id(actor),
@ -456,21 +409,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
# disallow objects with bogus IDs
defp handle_incoming_normalised(%{"id" => nil}, _options), do: :error
defp handle_incoming_normalised(%{"id" => ""}, _options), do: :error
def handle_incoming(%{"id" => nil}, _options), do: :error
def handle_incoming(%{"id" => ""}, _options), do: :error
# length of https:// = 8, should validate better, but good enough for now.
defp handle_incoming_normalised(%{"id" => id}, _options)
when is_binary(id) and byte_size(id) < 8,
do: :error
def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id) < 8,
do: :error
# Rewrite misskey likes into EmojiReacts
defp handle_incoming_normalised(
%{
"type" => "Like",
"content" => reaction
} = data,
options
) do
@doc "Rewrite misskey likes into EmojiReacts"
def handle_incoming(
%{
"type" => "Like",
"content" => reaction
} = data,
options
) do
if Pleroma.Emoji.is_unicode_emoji?(reaction) || Pleroma.Emoji.matches_shortcode?(reaction) do
data
|> Map.put("type", "EmojiReact")
@ -482,11 +434,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
defp handle_incoming_normalised(
%{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
options
)
when objtype in ~w{Question Answer Audio Video Event Article Note Page} do
def handle_incoming(
%{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
options
)
when objtype in ~w{Question Answer Audio Video Event Article Note Page} do
fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
object =
@ -518,8 +470,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
defp handle_incoming_normalised(%{"type" => type} = data, _options)
when type in ~w{Like EmojiReact Announce Add Remove} do
def handle_incoming(%{"type" => type} = data, _options)
when type in ~w{Like EmojiReact Announce Add Remove} do
with :ok <- ObjectValidator.fetch_actor_and_object(data),
{:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
@ -529,11 +481,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
defp handle_incoming_normalised(
%{"type" => type} = data,
_options
)
when type in ~w{Update Block Follow Accept Reject} do
def handle_incoming(
%{"type" => type} = data,
_options
)
when type in ~w{Update Block Follow Accept Reject} do
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
{:ok, activity, _} <-
Pipeline.common_pipeline(data, local: false) do
@ -541,10 +493,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
defp handle_incoming_normalised(
%{"type" => "Delete"} = data,
_options
) do
def handle_incoming(
%{"type" => "Delete"} = data,
_options
) do
with {:ok, activity, _} <-
Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
@ -564,15 +516,15 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
defp handle_incoming_normalised(
%{
"type" => "Undo",
"object" => %{"type" => "Follow", "object" => followed},
"actor" => follower,
"id" => id
} = _data,
_options
) do
def handle_incoming(
%{
"type" => "Undo",
"object" => %{"type" => "Follow", "object" => followed},
"actor" => follower,
"id" => id
} = _data,
_options
) do
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
{:ok, activity} <- ActivityPub.unfollow(follower, followed, id, false) do
@ -583,28 +535,28 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
defp handle_incoming_normalised(
%{
"type" => "Undo",
"object" => %{"type" => type}
} = data,
_options
)
when type in ["Like", "EmojiReact", "Announce", "Block"] do
def handle_incoming(
%{
"type" => "Undo",
"object" => %{"type" => type}
} = data,
_options
)
when type in ["Like", "EmojiReact", "Announce", "Block"] do
with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
end
end
# For Undos that don't have the complete object attached, try to find it in our database.
defp handle_incoming_normalised(
%{
"type" => "Undo",
"object" => object
} = activity,
options
)
when is_binary(object) do
def handle_incoming(
%{
"type" => "Undo",
"object" => object
} = activity,
options
)
when is_binary(object) do
with %Activity{data: data} <- Activity.get_by_ap_id(object) do
activity
|> Map.put("object", data)
@ -614,22 +566,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
defp handle_incoming_normalised(
%{
"type" => "Move",
"actor" => origin_actor,
"object" => origin_actor,
"target" => target_actor
},
_options
) do
def handle_incoming(
%{
"type" => "Move",
"actor" => origin_actor,
"object" => origin_actor,
"target" => target_actor
},
_options
) do
with %User{} = origin_user <- User.get_cached_by_ap_id(origin_actor),
# Use a dramatically shortened maximum age before refresh here because it is reasonable
# for a user to
# 1. Add the alias to their new account and then
# 2. Press the button on their new account
# within a very short period of time and expect it to work
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_actor, maximum_age: 5),
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_actor),
true <- origin_actor in target_user.also_known_as do
ActivityPub.move(origin_user, target_user, false)
else
@ -637,7 +584,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
defp handle_incoming_normalised(_, _), do: :error
def handle_incoming(_, _), do: :error
@spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
def get_obj_helper(id, options \\ []) do
@ -881,7 +828,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
relative_object do
Map.put(data, "object", external_url)
else
{:fetch, _} ->
{:fetch, e} ->
Logger.error("Couldn't fetch fixed_object@#{object} #{inspect(e)}")
data
_ ->

View File

@ -26,7 +26,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"oauthAuthorizationEndpoint" => url(~p"/oauth/authorize"),
"oauthRegistrationEndpoint" => url(~p"/api/v1/apps"),
"oauthTokenEndpoint" => url(~p"/oauth/token"),
"sharedInbox" => url(~p"/inbox")
"sharedInbox" => url(~p"/inbox"),
"uploadMedia" => url(~p"/api/ap/upload_media")
}
end

View File

@ -150,7 +150,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
"removes the contents of a message from the push notification"
)
],
requestBody: request_body("Parameters", update_notification_settings_request()),
requestBody: nil,
responses: %{
200 =>
Operation.response("Success", "application/json", %Schema{
@ -432,22 +432,4 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
}
}
end
defp update_notification_settings_request do
%Schema{
title: "UpdateNotificationSettings",
description: "PUT paramenters (query, form or JSON) for updating notification settings",
type: :object,
properties: %{
block_from_strangers: %Schema{
type: :boolean,
description: "blocks notifications from accounts you do not follow"
},
hide_notification_contents: %Schema{
type: :boolean,
description: "removes the contents of a message from the push notification"
}
}
}
end
end

View File

@ -98,10 +98,6 @@ defmodule Pleroma.Web.Endpoint do
at: "/",
from: :pleroma,
only: Pleroma.Web.static_paths(),
# JSON-LD is accepted by some servers for AP objects and activities,
# thus only enable it here instead of a global extension mapping
# (it's our only *.jsonld file anyway)
content_types: %{"litepub-0.1.jsonld" => "application/ld+json"},
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
gzip: true,
cache_control_for_etags: @static_cache_control,

View File

@ -78,7 +78,9 @@ defmodule Pleroma.Web.Feed.FeedView do
end
def activity_content(%{"content" => content}) do
escape(content)
content
|> String.replace(~r/[\n\r]/, "")
|> escape()
end
def activity_content(_), do: ""

View File

@ -145,7 +145,7 @@ defmodule Pleroma.Web.MediaProxy do
end
def base_url do
Config.get!([:media_proxy, :base_url])
Config.get([:media_proxy, :base_url], Endpoint.url())
end
defp proxy_url(path, sig_base64, url_base64, filename) do

View File

@ -12,38 +12,14 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
@behaviour Provider
@media_types ["image", "audio", "video"]
defp user_avatar_tags(user) do
if Utils.visible?(user) do
[
{:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))],
[]},
{:meta, [property: "og:image:width", content: 150], []},
{:meta, [property: "og:image:height", content: 150], []}
]
else
[]
end
end
@impl Provider
def build_tags(%{
object: object,
url: url,
user: user
}) do
attachments =
if Utils.visible?(object) do
build_attachments(object)
else
[]
end
scrubbed_content =
if Utils.visible?(object) do
Utils.scrub_html_and_truncate(object)
else
"Content cannot be displayed."
end
attachments = build_attachments(object)
scrubbed_content = Utils.scrub_html_and_truncate(object)
[
{:meta,
@ -60,7 +36,12 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
{:meta, [property: "og:type", content: "article"], []}
] ++
if attachments == [] or Metadata.activity_nsfw?(object) do
user_avatar_tags(user)
[
{:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))],
[]},
{:meta, [property: "og:image:width", content: 150], []},
{:meta, [property: "og:image:height", content: 150], []}
]
else
attachments
end
@ -68,9 +49,7 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
@impl Provider
def build_tags(%{user: user}) do
if Utils.visible?(user) do
truncated_bio = Utils.scrub_html_and_truncate(user.bio)
with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do
[
{:meta,
[
@ -79,10 +58,12 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
], []},
{:meta, [property: "og:url", content: user.uri || user.ap_id], []},
{:meta, [property: "og:description", content: truncated_bio], []},
{:meta, [property: "og:type", content: "article"], []}
] ++ user_avatar_tags(user)
else
[]
{:meta, [property: "og:type", content: "article"], []},
{:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))],
[]},
{:meta, [property: "og:image:width", content: 150], []},
{:meta, [property: "og:image:height", content: 150], []}
]
end
end

View File

@ -17,19 +17,8 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
@impl Provider
def build_tags(%{activity_id: id, object: object, user: user}) do
attachments =
if Utils.visible?(object) do
build_attachments(id, object)
else
[]
end
scrubbed_content =
if Utils.visible?(object) do
Utils.scrub_html_and_truncate(object)
else
"Content cannot be displayed."
end
attachments = build_attachments(id, object)
scrubbed_content = Utils.scrub_html_and_truncate(object)
[
title_tag(user),
@ -47,17 +36,13 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
@impl Provider
def build_tags(%{user: user}) do
if Utils.visible?(user) do
with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do
[
title_tag(user),
{:meta, [name: "twitter:description", content: truncated_bio], []},
image_tag(user),
{:meta, [name: "twitter:card", content: "summary"], []}
]
end
else
[]
with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do
[
title_tag(user),
{:meta, [name: "twitter:description", content: truncated_bio], []},
image_tag(user),
{:meta, [name: "twitter:card", content: "summary"], []}
]
end
end
@ -66,11 +51,7 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
end
def image_tag(user) do
if Utils.visible?(user) do
{:meta, [name: "twitter:image", content: MediaProxy.preview_url(User.avatar_url(user))], []}
else
{:meta, [name: "twitter:image", content: ""], []}
end
{:meta, [name: "twitter:image", content: MediaProxy.preview_url(User.avatar_url(user))], []}
end
defp build_attachments(id, %{data: %{"attachment" => attachments}}) do

View File

@ -7,15 +7,6 @@ defmodule Pleroma.Web.Metadata.Utils do
alias Pleroma.Emoji
alias Pleroma.Formatter
alias Pleroma.HTML
alias Pleroma.Web.ActivityPub.Visibility
def visible?(%Pleroma.User{} = object) do
Visibility.restrict_unauthenticated_access?(object) == :visible
end
def visible?(object) do
Visibility.visible_for_user?(object, nil)
end
defp scrub_html_and_truncate_object_field(field, object) do
field

View File

@ -3,12 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.InstanceStatic do
import Plug.Conn
require Pleroma.Constants
alias Pleroma.Web.Plugs.Utils
@moduledoc """
This is a shim to call `Plug.Static` but with runtime `from` configuration.
@ -47,25 +43,11 @@ defmodule Pleroma.Web.Plugs.InstanceStatic do
conn
end
defp set_static_content_type(conn, "/emoji/" <> _ = request_path) do
real_mime = MIME.from_path(request_path)
safe_mime = Utils.get_safe_mime_type(%{allowed_mime_types: ["image"]}, real_mime)
put_resp_header(conn, "content-type", safe_mime)
end
defp set_static_content_type(conn, request_path) do
put_resp_header(conn, "content-type", MIME.from_path(request_path))
end
defp call_static(%{request_path: request_path} = conn, opts, from) do
defp call_static(conn, opts, from) do
opts =
opts
|> Map.put(:from, from)
|> Map.put(:set_content_type, false)
conn
|> set_static_content_type(request_path)
|> Pleroma.Web.Plugs.StaticNoCT.call(opts)
Plug.Static.call(conn, opts)
end
end

View File

@ -1,469 +0,0 @@
# This is almost identical to Plug.Static from Plug 1.15.3 (2024-01-16)
# It being copied is a temporary measure to fix an urgent bug without
# needing to wait for merge of a suitable patch upstream
# The differences are:
# - this leading comment
# - renaming of the module from 'Plug.Static' to 'Pleroma.Web.Plugs.StaticNoCT'
# - additon of set_content_type option
defmodule Pleroma.Web.Plugs.StaticNoCT do
@moduledoc """
A plug for serving static assets.
It requires two options:
* `:at` - the request path to reach for static assets.
It must be a string.
* `:from` - the file system path to read static assets from.
It can be either: a string containing a file system path, an
atom representing the application name (where assets will
be served from `priv/static`), a tuple containing the
application name and the directory to serve assets from (besides
`priv/static`), or an MFA tuple.
The preferred form is to use `:from` with an atom or tuple, since
it will make your application independent from the starting directory.
For example, if you pass:
plug Plug.Static, from: "priv/app/path"
Plug.Static will be unable to serve assets if you build releases
or if you change the current directory. Instead do:
plug Plug.Static, from: {:app_name, "priv/app/path"}
If a static asset cannot be found, `Plug.Static` simply forwards
the connection to the rest of the pipeline.
## Cache mechanisms
`Plug.Static` uses etags for HTTP caching. This means browsers/clients
should cache assets on the first request and validate the cache on
following requests, not downloading the static asset once again if it
has not changed. The cache-control for etags is specified by the
`cache_control_for_etags` option and defaults to `"public"`.
However, `Plug.Static` also supports direct cache control by using
versioned query strings. If the request query string starts with
"?vsn=", `Plug.Static` assumes the application is versioning assets
and does not set the `ETag` header, meaning the cache behaviour will
be specified solely by the `cache_control_for_vsn_requests` config,
which defaults to `"public, max-age=31536000"`.
## Options
* `:encodings` - list of 2-ary tuples where first value is value of
the `Accept-Encoding` header and second is extension of the file to
be served if given encoding is accepted by client. Entries will be tested
in order in list, so entries higher in list will be preferred. Defaults
to: `[]`.
In addition to setting this value directly it supports 2 additional
options for compatibility reasons:
+ `:brotli` - will append `{"br", ".br"}` to the encodings list.
+ `:gzip` - will append `{"gzip", ".gz"}` to the encodings list.
Additional options will be added in the above order (Brotli takes
preference over Gzip) to reflect older behaviour which was set due
to fact that Brotli in general provides better compression ratio than
Gzip.
* `:cache_control_for_etags` - sets the cache header for requests
that use etags. Defaults to `"public"`.
* `:etag_generation` - specify a `{module, function, args}` to be used
to generate an etag. The `path` of the resource will be passed to
the function, as well as the `args`. If this option is not supplied,
etags will be generated based off of file size and modification time.
Note it is [recommended for the etag value to be quoted](https://tools.ietf.org/html/rfc7232#section-2.3),
which Plug won't do automatically.
* `:cache_control_for_vsn_requests` - sets the cache header for
requests starting with "?vsn=" in the query string. Defaults to
`"public, max-age=31536000"`.
* `:only` - filters which requests to serve. This is useful to avoid
file system access on every request when this plug is mounted
at `"/"`. For example, if `only: ["images", "favicon.ico"]` is
specified, only files in the "images" directory and the
"favicon.ico" file will be served by `Plug.Static`.
Note that `Plug.Static` matches these filters against request
uri and not against the filesystem. When requesting
a file with name containing non-ascii or special characters,
you should use urlencoded form. For example, you should write
`only: ["file%20name"]` instead of `only: ["file name"]`.
Defaults to `nil` (no filtering).
* `:only_matching` - a relaxed version of `:only` that will
serve any request as long as one of the given values matches the
given path. For example, `only_matching: ["images", "favicon"]`
will match any request that starts at "images" or "favicon",
be it "/images/foo.png", "/images-high/foo.png", "/favicon.ico"
or "/favicon-high.ico". Such matches are useful when serving
digested files at the root. Defaults to `nil` (no filtering).
* `:headers` - other headers to be set when serving static assets. Specify either
an enum of key-value pairs or a `{module, function, args}` to return an enum. The
`conn` will be passed to the function, as well as the `args`.
* `:content_types` - custom MIME type mapping. As a map with filename as key
and content type as value. For example:
`content_types: %{"apple-app-site-association" => "application/json"}`.
* `:set_content_type` - by default Plug.Static (re)sets the content type header
using auto-detection and the `:content_types` map. But when set to `false`
no content-type header will be inserted instead retaining the original
value or lack thereof. This can be useful when custom logic for appropiate
content types is needed which cannot be reasonably expressed as a static
filename map.
## Examples
This plug can be mounted in a `Plug.Builder` pipeline as follows:
defmodule MyPlug do
use Plug.Builder
plug Plug.Static,
at: "/public",
from: :my_app,
only: ~w(images robots.txt)
plug :not_found
def not_found(conn, _) do
send_resp(conn, 404, "not found")
end
end
"""
@behaviour Plug
@allowed_methods ~w(GET HEAD)
import Plug.Conn
alias Plug.Conn
# In this module, the `:prim_file` Erlang module along with the `:file_info`
# record are used instead of the more common and Elixir-y `File` module and
# `File.Stat` struct, respectively. The reason behind this is performance: all
# the `File` operations pass through a single process in order to support node
# operations that we simply don't need when serving assets.
require Record
Record.defrecordp(:file_info, Record.extract(:file_info, from_lib: "kernel/include/file.hrl"))
defmodule InvalidPathError do
defexception message: "invalid path for static asset", plug_status: 400
end
@impl true
def init(opts) do
from =
case Keyword.fetch!(opts, :from) do
{_, _} = from -> from
{_, _, _} = from -> from
from when is_atom(from) -> {from, "priv/static"}
from when is_binary(from) -> from
_ -> raise ArgumentError, ":from must be an atom, a binary or a tuple"
end
encodings =
opts
|> Keyword.get(:encodings, [])
|> maybe_add("br", ".br", Keyword.get(opts, :brotli, false))
|> maybe_add("gzip", ".gz", Keyword.get(opts, :gzip, false))
%{
encodings: encodings,
only_rules: {Keyword.get(opts, :only, []), Keyword.get(opts, :only_matching, [])},
qs_cache: Keyword.get(opts, :cache_control_for_vsn_requests, "public, max-age=31536000"),
et_cache: Keyword.get(opts, :cache_control_for_etags, "public"),
et_generation: Keyword.get(opts, :etag_generation, nil),
headers: Keyword.get(opts, :headers, %{}),
content_types: Keyword.get(opts, :content_types, %{}),
set_content_type: Keyword.get(opts, :set_content_type, true),
from: from,
at: opts |> Keyword.fetch!(:at) |> Plug.Router.Utils.split()
}
end
@impl true
def call(
conn = %Conn{method: meth},
%{at: at, only_rules: only_rules, from: from, encodings: encodings} = options
)
when meth in @allowed_methods do
segments = subset(at, conn.path_info)
if allowed?(only_rules, segments) do
segments = Enum.map(segments, &uri_decode/1)
if invalid_path?(segments) do
raise InvalidPathError, "invalid path for static asset: #{conn.request_path}"
end
path = path(from, segments)
range = get_req_header(conn, "range")
encoding = file_encoding(conn, path, range, encodings)
serve_static(encoding, conn, segments, range, options)
else
conn
end
end
def call(conn, _options) do
conn
end
defp uri_decode(path) do
# TODO: Remove rescue as this can't fail from Elixir v1.13
try do
URI.decode(path)
rescue
ArgumentError ->
raise InvalidPathError
end
end
defp allowed?(_only_rules, []), do: false
defp allowed?({[], []}, _list), do: true
defp allowed?({full, prefix}, [h | _]) do
h in full or (prefix != [] and match?({0, _}, :binary.match(h, prefix)))
end
defp maybe_put_content_type(conn, false, _, _), do: conn
defp maybe_put_content_type(conn, _, types, filename) do
content_type = Map.get(types, filename) || MIME.from_path(filename)
conn
|> put_resp_header("content-type", content_type)
end
defp serve_static({content_encoding, file_info, path}, conn, segments, range, options) do
%{
qs_cache: qs_cache,
et_cache: et_cache,
et_generation: et_generation,
headers: headers,
content_types: types,
set_content_type: set_content_type
} = options
case put_cache_header(conn, qs_cache, et_cache, et_generation, file_info, path) do
{:stale, conn} ->
filename = List.last(segments)
conn
|> maybe_put_content_type(set_content_type, types, filename)
|> put_resp_header("accept-ranges", "bytes")
|> maybe_add_encoding(content_encoding)
|> merge_headers(headers)
|> serve_range(file_info, path, range, options)
{:fresh, conn} ->
conn
|> maybe_add_vary(options)
|> send_resp(304, "")
|> halt()
end
end
defp serve_static(:error, conn, _segments, _range, _options) do
conn
end
defp serve_range(conn, file_info, path, [range], options) do
file_info(size: file_size) = file_info
with %{"bytes" => bytes} <- Plug.Conn.Utils.params(range),
{range_start, range_end} <- start_and_end(bytes, file_size) do
send_range(conn, path, range_start, range_end, file_size, options)
else
_ -> send_entire_file(conn, path, options)
end
end
defp serve_range(conn, _file_info, path, _range, options) do
send_entire_file(conn, path, options)
end
defp start_and_end("-" <> rest, file_size) do
case Integer.parse(rest) do
{last, ""} when last > 0 and last <= file_size -> {file_size - last, file_size - 1}
_ -> :error
end
end
defp start_and_end(range, file_size) do
case Integer.parse(range) do
{first, "-"} when first >= 0 ->
{first, file_size - 1}
{first, "-" <> rest} when first >= 0 ->
case Integer.parse(rest) do
{last, ""} when last >= first -> {first, min(last, file_size - 1)}
_ -> :error
end
_ ->
:error
end
end
defp send_range(conn, path, 0, range_end, file_size, options) when range_end == file_size - 1 do
send_entire_file(conn, path, options)
end
defp send_range(conn, path, range_start, range_end, file_size, _options) do
length = range_end - range_start + 1
conn
|> put_resp_header("content-range", "bytes #{range_start}-#{range_end}/#{file_size}")
|> send_file(206, path, range_start, length)
|> halt()
end
defp send_entire_file(conn, path, options) do
conn
|> maybe_add_vary(options)
|> send_file(200, path)
|> halt()
end
defp maybe_add_encoding(conn, nil), do: conn
defp maybe_add_encoding(conn, ce), do: put_resp_header(conn, "content-encoding", ce)
defp maybe_add_vary(conn, %{encodings: encodings}) do
# If we serve gzip or brotli at any moment, we need to set the proper vary
# header regardless of whether we are serving gzip content right now.
# See: http://www.fastly.com/blog/best-practices-for-using-the-vary-header/
if encodings != [] do
update_in(conn.resp_headers, &[{"vary", "Accept-Encoding"} | &1])
else
conn
end
end
defp put_cache_header(
%Conn{query_string: "vsn=" <> _} = conn,
qs_cache,
_et_cache,
_et_generation,
_file_info,
_path
)
when is_binary(qs_cache) do
{:stale, put_resp_header(conn, "cache-control", qs_cache)}
end
defp put_cache_header(conn, _qs_cache, et_cache, et_generation, file_info, path)
when is_binary(et_cache) do
etag = etag_for_path(file_info, et_generation, path)
conn =
conn
|> put_resp_header("cache-control", et_cache)
|> put_resp_header("etag", etag)
if etag in get_req_header(conn, "if-none-match") do
{:fresh, conn}
else
{:stale, conn}
end
end
defp put_cache_header(conn, _, _, _, _, _) do
{:stale, conn}
end
defp etag_for_path(file_info, et_generation, path) do
case et_generation do
{module, function, args} ->
apply(module, function, [path | args])
nil ->
file_info(size: size, mtime: mtime) = file_info
<<?", {size, mtime} |> :erlang.phash2() |> Integer.to_string(16)::binary, ?">>
end
end
defp file_encoding(conn, path, [_range], _encodings) do
# We do not support compression for range queries.
file_encoding(conn, path, nil, [])
end
defp file_encoding(conn, path, _range, encodings) do
encoded =
Enum.find_value(encodings, fn {encoding, ext} ->
if file_info = accept_encoding?(conn, encoding) && regular_file_info(path <> ext) do
{encoding, file_info, path <> ext}
end
end)
cond do
not is_nil(encoded) ->
encoded
file_info = regular_file_info(path) ->
{nil, file_info, path}
true ->
:error
end
end
defp regular_file_info(path) do
case :prim_file.read_file_info(path) do
{:ok, file_info(type: :regular) = file_info} ->
file_info
_ ->
nil
end
end
defp accept_encoding?(conn, encoding) do
encoding? = &String.contains?(&1, [encoding, "*"])
Enum.any?(get_req_header(conn, "accept-encoding"), fn accept ->
accept |> Plug.Conn.Utils.list() |> Enum.any?(encoding?)
end)
end
defp maybe_add(list, key, value, true), do: list ++ [{key, value}]
defp maybe_add(list, _key, _value, false), do: list
defp path({module, function, arguments}, segments)
when is_atom(module) and is_atom(function) and is_list(arguments),
do: Enum.join([apply(module, function, arguments) | segments], "/")
defp path({app, from}, segments) when is_atom(app) and is_binary(from),
do: Enum.join([Application.app_dir(app), from | segments], "/")
defp path(from, segments),
do: Enum.join([from | segments], "/")
defp subset([h | expected], [h | actual]), do: subset(expected, actual)
defp subset([], actual), do: actual
defp subset(_, _), do: []
defp invalid_path?(list) do
invalid_path?(list, :binary.compile_pattern(["/", "\\", ":", "\0"]))
end
defp invalid_path?([h | _], _match) when h in [".", "..", ""], do: true
defp invalid_path?([h | t], match), do: String.contains?(h, match) or invalid_path?(t)
defp invalid_path?([], _match), do: false
defp merge_headers(conn, {module, function, args}) do
merge_headers(conn, apply(module, function, [conn | args]))
end
defp merge_headers(conn, headers) do
merge_resp_headers(conn, headers)
end
end

View File

@ -11,7 +11,6 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do
require Logger
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.Plugs.Utils
@behaviour Plug
# no slashes
@ -29,21 +28,10 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do
|> Keyword.put(:at, "/__unconfigured_media_plug")
|> Plug.Static.init()
config = Pleroma.Config.get(Pleroma.Upload)
allowed_mime_types = Keyword.fetch!(config, :allowed_mime_types)
uploader = Keyword.fetch!(config, :uploader)
%{
static_plug_opts: static_plug_opts,
allowed_mime_types: allowed_mime_types,
uploader: uploader
}
%{static_plug_opts: static_plug_opts}
end
def call(
%{request_path: <<"/", @path, "/", file::binary>>} = conn,
%{uploader: uploader} = opts
) do
def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
conn =
case fetch_query_params(conn) do
%{query_params: %{"name" => name}} = conn ->
@ -56,7 +44,10 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do
end
|> merge_resp_headers([{"content-security-policy", "sandbox"}])
with {:ok, get_method} <- uploader.get_file(file),
config = Pleroma.Config.get(Pleroma.Upload)
with uploader <- Keyword.fetch!(config, :uploader),
{:ok, get_method} <- uploader.get_file(file),
false <- media_is_banned(conn, get_method) do
get_media(conn, get_method, opts)
else
@ -77,23 +68,13 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do
defp media_is_banned(_, _), do: false
defp set_content_type(conn, opts, filepath) do
real_mime = MIME.from_path(filepath)
clean_mime = Utils.get_safe_mime_type(opts, real_mime)
put_resp_header(conn, "content-type", clean_mime)
end
defp get_media(conn, {:static_dir, directory}, opts) do
static_opts =
Map.get(opts, :static_plug_opts)
|> Map.put(:at, [@path])
|> Map.put(:from, directory)
|> Map.put(:set_content_type, false)
conn =
conn
|> set_content_type(opts, conn.request_path)
|> Pleroma.Web.Plugs.StaticNoCT.call(static_opts)
conn = Plug.Static.call(conn, static_opts)
if conn.halted do
conn

View File

@ -1,14 +0,0 @@
# Akkoma: Magically expressive social media
# Copyright © 2024 Akkoma Authors <https://akkoma.dev>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.Utils do
@moduledoc """
Some helper functions shared across several plugs
"""
def get_safe_mime_type(%{allowed_mime_types: allowed_mime_types} = _opts, mime) do
[maintype | _] = String.split(mime, "/", parts: 2)
if maintype in allowed_mime_types, do: mime, else: "application/octet-stream"
end
end

View File

@ -800,9 +800,13 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web.ActivityPub do
pipe_through([:activitypub_client])
get("/api/ap/whoami", ActivityPubController, :whoami)
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
get("/users/:nickname/outbox", ActivityPubController, :outbox)
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
post("/api/ap/upload_media", ActivityPubController, :upload_media)
get("/users/:nickname/collections/featured", ActivityPubController, :pinned)
end

View File

@ -24,13 +24,7 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
true <- Visibility.is_public?(activity.object),
{_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
%User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do
meta =
Metadata.build_tags(%{
activity_id: notice_id,
url: activity.data["id"],
object: activity.object,
user: user
})
meta = Metadata.build_tags(%{url: activity.data["id"], object: activity.object, user: user})
timeline =
activity.object.data["context"]

View File

@ -151,40 +151,41 @@ defmodule Pleroma.Web.Telemetry do
# phoenix.router_dispatch.stop.duration
# pleroma.repo.query.total_time
# pleroma.repo.query.queue_time
dist_metrics = [
distribution("phoenix.endpoint.stop.duration.fdist",
event_name: [:phoenix, :endpoint, :stop],
measurement: :duration,
unit: {:native, :millisecond},
reporter_options: [
buckets: simple_buckets
]
),
distribution("pleroma.repo.query.decode_time.fdist",
event_name: [:pleroma, :repo, :query],
measurement: :decode_time,
unit: {:native, :millisecond},
reporter_options: [
buckets: simple_buckets_quick
]
),
distribution("pleroma.repo.query.query_time.fdist",
event_name: [:pleroma, :repo, :query],
measurement: :query_time,
unit: {:native, :millisecond},
reporter_options: [
buckets: simple_buckets
]
),
distribution("pleroma.repo.query.idle_time.fdist",
event_name: [:pleroma, :repo, :query],
measurement: :idle_time,
unit: {:native, :millisecond},
reporter_options: [
buckets: simple_buckets
]
)
]
dist_metrics =
[
distribution("phoenix.endpoint.stop.duration.fdist",
event_name: [:phoenix, :endpoint, :stop],
measurement: :duration,
unit: {:native, :millisecond},
reporter_options: [
buckets: simple_buckets
]
),
distribution("pleroma.repo.query.decode_time.fdist",
event_name: [:pleroma, :repo, :query],
measurement: :decode_time,
unit: {:native, :millisecond},
reporter_options: [
buckets: simple_buckets_quick
]
),
distribution("pleroma.repo.query.query_time.fdist",
event_name: [:pleroma, :repo, :query],
measurement: :query_time,
unit: {:native, :millisecond},
reporter_options: [
buckets: simple_buckets
]
),
distribution("pleroma.repo.query.idle_time.fdist",
event_name: [:pleroma, :repo, :query],
measurement: :idle_time,
unit: {:native, :millisecond},
reporter_options: [
buckets: simple_buckets
]
)
]
vm_metrics =
sum_counter_pair("vm.memory.total",

View File

@ -184,13 +184,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
json(conn, emoji)
end
def update_notificaton_settings(
%{assigns: %{user: user}, body_params: body_params} = conn,
params
) do
# OpenApiSpex 3.x prevents Plug's usual parameter premerging
params = Map.merge(params, body_params)
def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
with {:ok, _} <- User.update_notification_settings(user, params) do
json(conn, %{status: "success"})
end

View File

@ -65,7 +65,7 @@ defmodule Pleroma.Web.WebFinger do
end
defp gather_aliases(%User{} = user) do
[user.ap_id]
[user.ap_id | user.also_known_as]
end
def represent_user(user, "JSON") do

View File

@ -26,7 +26,7 @@ defmodule Pleroma.Web.XML do
def parse_document(text) do
try do
doc = SweetXml.parse(text, dtd: :none, quiet: true)
doc = SweetXml.parse(text, dtd: :none)
{:ok, doc}
rescue

View File

@ -21,9 +21,6 @@ defmodule Pleroma.Workers.Cron.PruneDatabaseWorker do
Logger.info("Pruning old undos")
ActivityPruner.prune_undos()
Logger.info("Pruning old updates")
ActivityPruner.prune_updates()
Logger.info("Pruning old removes")
ActivityPruner.prune_removes()

View File

@ -14,8 +14,7 @@ defmodule Pleroma.Workers.ReceiverWorker do
else
{:error, :origin_containment_failed} -> {:discard, :origin_containment_failed}
{:error, {:reject, reason}} -> {:discard, reason}
{:error, _} = e -> e
e -> {:error, e}
e -> e
end
end
end

View File

@ -5,42 +5,10 @@
defmodule Pleroma.Workers.RemoteFetcherWorker do
alias Pleroma.Object.Fetcher
use Pleroma.Workers.WorkerHelper,
queue: "remote_fetcher",
unique: [period: 300, states: Oban.Job.states(), keys: [:op, :id]]
use Pleroma.Workers.WorkerHelper, queue: "remote_fetcher"
@impl Oban.Worker
def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
case Fetcher.fetch_object_from_id(id, depth: args["depth"]) do
{:ok, _object} ->
:ok
{:error, :forbidden} ->
{:discard, :forbidden}
{:error, :not_found} ->
{:discard, :not_found}
{:error, :allowed_depth} ->
{:discard, :allowed_depth}
{:error, :invalid_uri_scheme} ->
{:discard, :invalid_uri_scheme}
{:error, :local_resource} ->
{:discard, :local_resource}
{:reject, _} ->
{:discard, :reject}
{:error, :id_mismatch} ->
{:discard, :id_mismatch}
{:error, _} = e ->
e
e ->
{:error, e}
end
{:ok, _object} = Fetcher.fetch_object_from_id(id, depth: args["depth"])
end
end

View File

@ -25,16 +25,12 @@ defmodule Pleroma.Workers.WorkerHelper do
defmacro __using__(opts) do
caller_module = __CALLER__.module
queue = Keyword.fetch!(opts, :queue)
# by default just stop unintended duplicates - this can and should be overridden
# if you want to have a more complex uniqueness constraint
uniqueness = Keyword.get(opts, :unique, period: 1)
quote do
# Note: `max_attempts` is intended to be overridden in `new/2` call
use Oban.Worker,
queue: unquote(queue),
max_attempts: 1,
unique: unquote(uniqueness)
max_attempts: 1
alias Oban.Job

16
mix.exs
View File

@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
def project do
[
app: :pleroma,
version: version("3.13.1"),
version: version("3.10.3"),
elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
@ -21,13 +21,13 @@ defmodule Pleroma.Mixfile do
source_url: "https://akkoma.dev/AkkomaGang/akkoma",
docs: [
source_url_pattern: "https://akkoma.dev/AkkomaGang/akkoma/blob/develop/%{path}#L%{line}",
logo: "priv/static/logo-512.png",
extras: ["README.md", "CHANGELOG.md"] ++ Path.wildcard("docs/docs/**/*.md"),
logo: "priv/static/images/logo.png",
extras: ["README.md", "CHANGELOG.md"] ++ Path.wildcard("docs/**/*.md"),
groups_for_extras: [
"Installation manuals": Path.wildcard("docs/docs/installation/*.md"),
Configuration: Path.wildcard("docs/docs/config/*.md"),
Administration: Path.wildcard("docs/docs/admin/*.md"),
"Pleroma's APIs and Mastodon API extensions": Path.wildcard("docs/docs/api/*.md")
"Installation manuals": Path.wildcard("docs/installation/*.md"),
Configuration: Path.wildcard("docs/config/*.md"),
Administration: Path.wildcard("docs/admin/*.md"),
"Pleroma's APIs and Mastodon API extensions": Path.wildcard("docs/api/*.md")
],
main: "readme",
output: "priv/static/doc"
@ -125,7 +125,7 @@ defmodule Pleroma.Mixfile do
{:ecto_enum, "~> 1.4"},
{:ecto_sql, "~> 3.10.0"},
{:postgrex, "~> 0.17.2"},
{:oban, "~> 2.17.8"},
{:oban, "~> 2.15.2"},
{:gettext, "~> 0.22.3"},
{:bcrypt_elixir, "~> 3.0.1"},
{:fast_sanitize, "~> 0.2.3"},

View File

@ -3,55 +3,55 @@
"base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"},
"bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"},
"benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"benchee": {:hex, :benchee, "1.2.0", "afd2f0caec06ce3a70d9c91c514c0b58114636db9d83c2dc6bfd416656618353", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "ee729e53217898b8fd30aaad3cce61973dab61574ae6f48229fe7ff42d5e4457"},
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
"cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"},
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "90f6ce7672f70f56708792a98d98bd05176c9176", [ref: "90f6ce7672f70f56708792a98d98bd05176c9176"]},
"castore": {:hex, :castore, "1.0.6", "ffc42f110ebfdafab0ea159cd43d31365fa0af0ce4a02ecebf1707ae619ee727", [:mix], [], "hexpm", "374c6e7ca752296be3d6780a6d5b922854ffcc74123da90f2f328996b962d33a"},
"castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"},
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
"concurrent_limiter": {:git, "https://akkoma.dev/AkkomaGang/concurrent-limiter.git", "a9e0b3d64574bdba761f429bb4fba0cf687b3338", [ref: "a9e0b3d64574bdba761f429bb4fba0cf687b3338"]},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"},
"cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
"credo": {:hex, :credo, "1.7.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"},
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
"credo": {:hex, :credo, "1.7.1", "6e26bbcc9e22eefbff7e43188e69924e78818e2fe6282487d0703652bc20fd62", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e9871c6095a4c0381c89b6aa98bc6260a8ba6addccf7f6a53da8849c748a58a2"},
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
"db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
"dialyxir": {:hex, :dialyxir, "1.4.2", "764a6e8e7a354f0ba95d58418178d486065ead1f69ad89782817c296d0d746a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "516603d8067b2fd585319e4b13d3674ad4f314a5902ba8130cd97dc902ce6bbd"},
"earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
"earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
"eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"},
"ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.15", "0fc29dbae0e444a29bd6abeee4cf3c4c037e692a272478a234a1cc765077dbb1", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "b6127f3a5c6fc3d84895e4768cc7c199f22b48b67d6c99b13fbf4a374e73f039"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.14", "7a20cfe913b0476542b43870e67386461258734896035e3f284039fd18bd4c4c", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "22f5f98592dd597db9416fcef00effae0787669fdcb6faf447e982b553798e98"},
"ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"},
"elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]},
"elixir_make": {:hex, :elixir_make, "0.8.3", "d38d7ee1578d722d89b4d452a3e36bcfdc644c618f0d063b874661876e708683", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "5c99a18571a756d4af7a4d89ca75c28ac899e6103af6f223982f09ce44942cc9"},
"elixir_xml_to_map": {:hex, :elixir_xml_to_map, "3.1.0", "4d6260486a8cce59e4bf3575fe2dd2a24766546ceeef9f93fcec6f7c62a2827a", [:mix], [{:erlsom, "~> 1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm", "8fe5f2e75f90bab07ee2161120c2dc038ebcae8135554f5582990f1c8c21f911"},
"elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"},
"elixir_xml_to_map": {:hex, :elixir_xml_to_map, "3.0.0", "67dcff30ecf72aed37ab08525133e4420717a749436e22bfece431e7dddeea7e", [:mix], [{:erlsom, "~> 1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm", "11222dd7f029f8db7a6662b41c992dbdb0e1c6e4fdea6a42056f9d27c847efbb"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"erlsom": {:hex, :erlsom, "1.5.1", "c8fe2babd33ff0846403f6522328b8ab676f896b793634cfe7ef181c05316c03", [:rebar3], [], "hexpm", "7965485494c5844dd127656ac40f141aadfa174839ec1be1074e7edf5b4239eb"},
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
"ex_aws": {:hex, :ex_aws, "2.5.3", "9c2d05ba0c057395b12c7b5ca6267d14cdaec1d8e65bdf6481fe1fd245accfb4", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "67115f1d399d7ec4d191812ee565c6106cb4b1bbf19a9d4db06f265fd87da97e"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.3", "422468e5c3e1a4da5298e66c3468b465cfd354b842e512cb1f6fbbe4e2f5bdaf", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8"},
"ex_aws": {:hex, :ex_aws, "2.5.0", "1785e69350b16514c1049330537c7da10039b1a53e1d253bbd703b135174aec3", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "971b86e5495fc0ae1c318e35e23f389e74cf322f2c02d34037c6fc6d405006f1"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.2", "cee302b8e9ee198cc0d89f1de2a7d6a8921e1a556574476cf5590d2156590fe3", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "cc5bd945a22a99eece4721d734ae2452d3717e81c357a781c8574663254df4a1"},
"ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
"ex_doc": {:hex, :ex_doc, "0.32.0", "896afb57b1e00030f6ec8b2e19d3ca99a197afb23858d49d94aea673dc222f12", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "ed2c3e42c558f49bda3ff37e05713432006e1719a6c4a3320c7e4735787374e7"},
"ex_doc": {:hex, :ex_doc, "0.31.0", "06eb1dfd787445d9cab9a45088405593dd3bb7fe99e097eaa71f37ba80c7a676", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5350cafa6b7f77bdd107aa2199fe277acf29d739aba5aee7e865fc680c62a110"},
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
"ex_syslogger": {:hex, :ex_syslogger, "2.0.0", "de6de5c5472a9c4fdafb28fa6610e381ae79ebc17da6490b81d785d68bd124c9", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "a52b2fe71764e9e6ecd149ab66635812f68e39279cbeee27c52c0e35e8b8019e"},
"excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"},
"expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
"fast_html": {:hex, :fast_html, "2.3.0", "08c1d8ead840dd3060ba02c761bed9f37f456a1ddfe30bcdcfee8f651cec06a6", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "f18e3c7668f82d3ae0b15f48d48feeb257e28aa5ab1b0dbf781c7312e5da029d"},
"fast_html": {:hex, :fast_html, "2.2.0", "6c5ef1be087a4ed613b0379c13f815c4d11742b36b67bb52cee7859847c84520", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "064c4f23b4a6168f9187dac8984b056f2c531bb0787f559fd6a8b34b38aefbae"},
"fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"},
"file_ex": {:git, "https://akkoma.dev/AkkomaGang/file_ex.git", "cc7067c7d446c2526e9ecf91d40896b088851569", [ref: "cc7067c7d446c2526e9ecf91d40896b088851569"]},
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"},
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
"floki": {:hex, :floki, "0.36.1", "712b7f2ba19a4d5a47dfe3e74d81876c95bbcbee44fe551f0af3d2a388abb3da", [:mix], [], "hexpm", "21ba57abb8204bcc70c439b423fc0dd9f0286de67dc82773a14b0200ada0995f"},
"floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"},
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
"gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"},
"hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
@ -60,17 +60,17 @@
"http_signatures": {:git, "https://akkoma.dev/AkkomaGang/http_signatures.git", "6640ce7d24c783ac2ef56e27d00d12e8dc85f396", [ref: "6640ce7d24c783ac2ef56e27d00d12e8dc85f396"]},
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"},
"inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"joken": {:hex, :joken, "2.6.1", "2ca3d8d7f83bf7196296a3d9b2ecda421a404634bfc618159981a960020480a1", [:mix], [{:jose, "~> 1.11.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "ab26122c400b3d254ce7d86ed066d6afad27e70416df947cdcb01e13a7382e68"},
"jose": {:hex, :jose, "1.11.9", "c861eb99d9e9f62acd071dc5a49ffbeab9014e44490cd85ea3e49e3d36184777", [:mix, :rebar3], [], "hexpm", "b5ccc3749d2e1638c26bed806259df5bc9e438797fe60dc71e9fa0716133899b"},
"joken": {:hex, :joken, "2.6.0", "b9dd9b6d52e3e6fcb6c65e151ad38bf4bc286382b5b6f97079c47ade6b1bcc6a", [:mix], [{:jose, "~> 1.11.5", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5a95b05a71cd0b54abd35378aeb1d487a23a52c324fa7efdffc512b655b5aaa7"},
"jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"},
"jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
"linkify": {:git, "https://akkoma.dev/AkkomaGang/linkify.git", "2567e2c1073fa371fd26fd66dfa5bc77b6919c16", []},
"mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"},
"majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "80540b36939ec83f48e76c61e5000e0fd67706f0", [ref: "80540b36939ec83f48e76c61e5000e0fd67706f0"]},
"makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "b21ab7754024af096f2d14247574f55f0063295b", [ref: "b21ab7754024af096f2d14247574f55f0063295b"]},
@ -82,54 +82,54 @@
"mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"},
"nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"oban": {:hex, :oban, "2.17.8", "7fd7c8e82c7819afc1b5b5ed8d6d92bf0ecdd7ba170328fb043301eb06d32521", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a2165bf93843b7bcb68182c82725ddd4cb43c0c3719f114e7aa3b6c99c4b6129"},
"open_api_spex": {:hex, :open_api_spex, "3.18.3", "fefb84fe323cacfc92afdd0ecb9e89bc0261ae00b7e3167ffc2028ce3944de42", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "c0cfc31570199ce7e7520b494a591027da609af45f6bf9adce51e2469b1609fb"},
"nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"},
"oban": {:hex, :oban, "2.15.4", "d49ab4ffb7153010e32f80fe9e56f592706238149ec579eb50f8a4e41d218856", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5fce611fdfffb13e9148df883116e5201adf1e731eb302cc88cde0588510079c"},
"open_api_spex": {:hex, :open_api_spex, "3.18.0", "f9952b6bc8a1bf14168f3754981b7c8d72d015112bfedf2588471dd602e1e715", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "37849887ab67efab052376401fac28c0974b273ffaecd98f4532455ca0886464"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
"phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.5.1", "6fdbc334ea53620e71655664df6f33f670747b3a7a6c4041cdda3e2c32df6257", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ebe43aa580db129e54408e719fb9659b7f9e0d52b965c5be26cdca416ecead28"},
"phoenix": {:hex, :phoenix, "1.7.10", "02189140a61b2ce85bb633a9b6fd02dff705a5f1596869547aeb2b2b95edd729", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "cf784932e010fd736d656d7fead6a584a4498efefe5b8227e9f383bf15bb79d0"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.3", "86e9878f833829c3f66da03d75254c155d91d72a201eb56ae83482328dc7ca93", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d36c401206f3011fefd63d04e8ef626ec8791975d9d107f9a0817d426f61ac07"},
"phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"},
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.0", "a544d83fde4a767efb78f45404a74c9e37b2a9c5ea3339692e65a6966731f935", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba"},
"phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"},
"phoenix_view": {:hex, :phoenix_view, "2.0.3", "4d32c4817fce933693741deeb99ef1392619f942633dde834a5163124813aad3", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "cd34049af41be2c627df99cd4eaa71fc52a328c0c3d8e7d4aa28f880c30e7f64"},
"plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"},
"plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"},
"plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
"plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"},
"postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"},
"pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"recon": {:hex, :recon, "2.5.5", "c108a4c406fa301a529151a3bb53158cadc4064ec0c5f99b03ddb8c0e4281bdf", [:mix, :rebar3], [], "hexpm", "632a6f447df7ccc1a4a10bdcfce71514412b16660fe59deca0fcf0aa3c054404"},
"recon": {:hex, :recon, "2.5.4", "05dd52a119ee4059fa9daa1ab7ce81bc7a8161a2f12e9d42e9d551ffd2ba901c", [:mix, :rebar3], [], "hexpm", "e9ab01ac7fc8572e41eb59385efeb3fb0ff5bf02103816535bacaedf327d0263"},
"remote_ip": {:hex, :remote_ip, "1.1.0", "cb308841595d15df3f9073b7c39243a1dd6ca56e5020295cb012c76fbec50f2d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74"},
"search_parser": {:git, "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", "08971a81e68686f9ac465cfb6661d51c5e4e1e7f", [ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"]},
"sleeplocks": {:hex, :sleeplocks, "1.1.2", "d45aa1c5513da48c888715e3381211c859af34bee9b8290490e10c90bb6ff0ca", [:rebar3], [], "hexpm", "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
"sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"},
"swoosh": {:hex, :swoosh, "1.14.4", "94e9dba91f7695a10f49b0172c4a4cb658ef24abef7e8140394521b7f3bbb2d4", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "081c5a590e4ba85cc89baddf7b2beecf6c13f7f84a958f1cd969290815f0f026"},
"swoosh": {:hex, :swoosh, "1.14.2", "cf686f92ad3b21e6651b20c50eeb1781f581dc7097ef6251b4d322a9f1d19339", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01d8fae72930a0b5c1bb9725df0408602ed8c5c3d59dc6e7a39c57b723cd1065"},
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
"table_rex": {:hex, :table_rex, "4.0.0", "3c613a68ebdc6d4d1e731bc973c233500974ec3993c99fcdabb210407b90959b", [:mix], [], "hexpm", "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55"},
"table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
"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_prometheus": {:hex, :telemetry_metrics_prometheus, "1.1.0", "1cc23e932c1ef9aa3b91db257ead31ea58d53229d407e059b29bb962c1505a13", [:mix], [{:plug_cowboy, "~> 2.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}], "hexpm", "d43b3659b3244da44fe0275b717701542365d4519b79d9ce895b9719c1ce4d26"},
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"},
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
"temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]},
"tesla": {:hex, :tesla, "1.9.0", "8c22db6a826e56a087eeb8cdef56889731287f53feeb3f361dec5d4c8efb6f14", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [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]}, {:msgpax, "~> 2.3", [hex: :msgpax, 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", "7c240c67e855f7e63e795bf16d6b3f5115a81d1f44b7fe4eadbf656bae0fef8a"},
"tesla": {:hex, :tesla, "1.8.0", "d511a4f5c5e42538d97eef7c40ec4f3e44effdc5068206f42ed859e09e51d1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [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]}, {:msgpax, "~> 2.3", [hex: :msgpax, 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", "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f"},
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
"tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"},
"ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"},
"vex": {:hex, :vex, "0.9.2", "fe061acc9e0907d983d46b51bf35d58176f0fe6eb7ba3b33c9336401bf42b6d1", [:mix], [], "hexpm", "76e709a9762e98c6b462dfce92e9b5dfbf712839227f2da8add6dd11549b12cb"},
"vex": {:hex, :vex, "0.9.1", "cb65348ebd1c4002861b65bef36e524c29d9a879c90119b2d0e674e323124277", [:mix], [], "hexpm", "a0f9f3959d127ad6a6a617c3f607ecfb1bc6f3c59f9c3614a901a46d1765bafe"},
"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"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"},
"websock_adapter": {:hex, :websock_adapter, "0.5.5", "9dfeee8269b27e958a65b3e235b7e447769f66b5b5925385f5a569269164a210", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "4b977ba4a01918acbf77045ff88de7f6972c2a009213c515a445c48f224ffce9"},
"websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
}

View File

@ -3,16 +3,14 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-07-29 11:37+0000\n"
"PO-Revision-Date: 2024-04-13 22:55+0000\n"
"Last-Translator: fadelkon <fadelkon@posteo.net>\n"
"Language-Team: Catalan <http://translate.akkoma.dev/projects/akkoma/"
"akkoma-backend-errors/ca/>\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: ca\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.18.2\n"
"X-Generator: Translate Toolkit 3.7.1\n"
# # This file is a PO Template file.
# #
@ -612,46 +610,46 @@ msgstr "El teu compte espera aprovació."
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:262
#, elixir-autogen, elixir-format
msgid "File is too large"
msgstr "L'arxiu és massa gran"
msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:37
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:48
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:59
#, elixir-autogen, elixir-format
#, elixir-autogen, elixir-format, fuzzy
msgid "Hashtag not found"
msgstr "No s'ha trobat l'etiqueta"
msgstr "Llista no trobada"
#: lib/pleroma/web/common_api/activity_draft.ex:144
#, elixir-autogen, elixir-format
msgid "Invalid language"
msgstr "Llengua incompatible"
msgstr ""
#: lib/pleroma/web/o_auth/o_auth_controller.ex:218
#, elixir-autogen, elixir-format
#, elixir-autogen, elixir-format, fuzzy
msgid "This action is outside of authorized scopes"
msgstr "Aquesta acció és fora dels àmbits autoritzats"
#: lib/pleroma/web/common_api/activity_draft.ex:129
#, elixir-autogen, elixir-format
msgid "You can only quote public or unlisted statuses"
msgstr "Només pots citar publicacions desllistades o públiques"
msgstr ""
#: lib/pleroma/web/common_api/activity_draft.ex:126
#, elixir-autogen, elixir-format
msgid "You can't quote a status that doesn't exist"
msgstr "No pots citar una publicació que no existeix"
msgstr ""
#: lib/pleroma/web/embed_controller.ex:35
#, elixir-autogen, elixir-format
msgid "Federated posts cannot be embedded"
msgstr "No es poden incrustar publicacions federades"
msgstr ""
#: lib/pleroma/web/embed_controller.ex:38
#, elixir-autogen, elixir-format
msgid "Not authorized to view this post"
msgstr "No tens permís per veure aquesta publicació"
msgstr ""
#: lib/pleroma/web/embed_controller.ex:32
#, elixir-autogen, elixir-format
#, elixir-autogen, elixir-format, fuzzy
msgid "Post not found"
msgstr "No s'ha trobat la publicació"
msgstr "Llista no trobada"

View File

@ -3,8 +3,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-07-28 09:15+0000\n"
"PO-Revision-Date: 2024-04-13 22:55+0000\n"
"Last-Translator: fadelkon <fadelkon@posteo.net>\n"
"PO-Revision-Date: 2022-07-30 21:58+0000\n"
"Last-Translator: sola <spla@mastodont.cat>\n"
"Language-Team: Catalan <http://translate.akkoma.dev/projects/akkoma/"
"akkoma-backend-static-pages/ca/>\n"
"Language: ca\n"
@ -12,7 +12,7 @@ msgstr ""
"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.18.2\n"
"X-Generator: Weblate 4.13.1\n"
## This file is a PO Template file.
##
@ -531,25 +531,25 @@ msgid "Welcome to %{instance_name}!"
msgstr "Benvingut a %{instance_name}!"
#: lib/pleroma/emails/user_email.ex:368
#, elixir-autogen, elixir-format
#, elixir-autogen, elixir-format, fuzzy
msgctxt "account archive email body - admin requested"
msgid "<p>Admin @%{admin_nickname} requested a full backup of your Akkoma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
msgstr ""
"<p>L'Administrador @%{admin_nickname} ha sol·licitat una còpia de seguretat "
"completa del teu compte Akkoma. Està preparada per descarregar:</p>\n"
"<p>L'Administrador @%{admin_nickname} ha sol·licitat una copia de seguretat "
"completa del teu compte Akkoma. Està preparat per a descarrega:</p>\n"
"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
#: lib/pleroma/emails/user_email.ex:356
#, elixir-autogen, elixir-format
#, elixir-autogen, elixir-format, fuzzy
msgctxt "account archive email body - self-requested"
msgid "<p>You requested a full backup of your Akkoma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
msgstr ""
"<p>Has sol·licitat una còpia de seguretat completa del teu compte Akkoma. Ja "
"la pots descarregar:</p>\n"
"<p>Has sol·licitat una copia de seguretat completa del teu compte Akkoma. "
"Està llest per a descarrega:</p>\n"
"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:41
#, elixir-autogen, elixir-format
#, elixir-autogen, elixir-format, fuzzy
msgctxt "oauth register page title"
msgid "This is your first visit! Please enter your Akkoma handle."
msgstr ""
@ -560,22 +560,22 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "remote follow error message - unknown error"
msgid "Something went wrong."
msgstr "Hi ha hagut algun problema."
msgstr ""
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:67
#, elixir-autogen, elixir-format
msgctxt "remote follow error message - user not found"
msgid "Could not find user"
msgstr "No s'ha trobat l'usuari/a/ï"
msgstr ""
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "status interact authorization button"
msgid "Interact"
msgstr "Interacciona"
msgstr ""
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:2
#, elixir-autogen, elixir-format
#, elixir-autogen, elixir-format, fuzzy
msgctxt "status interact error"
msgid "Error: %{error}"
msgstr "Error: %{error}"
@ -584,33 +584,33 @@ msgstr "Error: %{error}"
#, elixir-autogen, elixir-format
msgctxt "status interact error message - status not found"
msgid "Could not find status"
msgstr "No s'ha trobat l'estat"
msgstr ""
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:144
#, elixir-autogen, elixir-format
msgctxt "status interact error message - unknown error"
msgid "Something went wrong."
msgstr "Hi ha hagut algun problema."
msgstr ""
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "status interact header"
msgid "Interacting with %{nickname}'s %{status_link}"
msgstr "S'està interactuant amb %{status_link} de %{nickname}"
msgstr ""
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "status interact header - status link text"
msgid "status"
msgstr "estat"
msgstr ""
#: lib/pleroma/emails/user_email.ex:119
#, elixir-autogen, elixir-format
#, elixir-autogen, elixir-format, fuzzy
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 Akkoma federated social networking platform.</p>\n<p>Click the following link to register: <a href=\"%{registration_url}\">accept invitation</a>.</p>\n"
msgstr ""
"<h3>T'han convidat a %{instance_name}</h3>\n"
"<p>%{inviter_name} t'anima a unir-te a %{instance_name}, una instància de la "
"plataforma de xarxa social federada Akkoma.</p>\n"
"<h3>Has estat invitat a %{instance_name}</h3>\n"
"<p>%{inviter_name} t'invita a unir-te a %{instance_name}, una instància de "
"la plataforma de xarxa social federada Akkoma.</p>\n"
"<p>Clica el següent enllaç per a registrar-te: <a href=\"%{registration_url}"
"\">accepta la invitació</a>.</p>\n"
"\">accepta invitació</a>.</p>\n"

File diff suppressed because it is too large Load Diff

View File

@ -8,154 +8,146 @@
### to merge POT files into PO files.
msgid ""
msgstr ""
"PO-Revision-Date: 2023-10-09 18:53+0000\n"
"Last-Translator: subtype <subtype@hollow.capital>\n"
"Language-Team: Polish <http://translate.akkoma.dev/projects/akkoma/"
"akkoma-backend-posix-errors/pl/>\n"
"Language: pl\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 4.18.2\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10||n%100>=20) ? 1 : 2);\n"
msgid "eperm"
msgstr "Operacja niedozwolona"
msgstr ""
msgid "eacces"
msgstr "Brak dostępu"
msgstr ""
msgid "eagain"
msgstr "Zasoby chwilowo niedostępne"
msgstr ""
msgid "ebadf"
msgstr "Błędny deskryptor pliku"
msgstr ""
msgid "ebadmsg"
msgstr "Błędny komunikat"
msgstr ""
msgid "ebusy"
msgstr "Urządzenie lub zasoby zajęte"
msgstr ""
msgid "edeadlk"
msgstr "Uniknięto zakleszczenia zasobów"
msgstr ""
msgid "edeadlock"
msgstr "Uniknięto zakleszczenia zasobów"
msgstr ""
msgid "edquot"
msgstr "Przekroczony limit dyskowy"
msgstr ""
msgid "eexist"
msgstr "Plik istnieje"
msgstr ""
msgid "efault"
msgstr "Błędny adres"
msgstr ""
msgid "efbig"
msgstr "Plik zbyt duży"
msgstr ""
msgid "eftype"
msgstr "Niewłaściwy typ pliku"
msgstr ""
msgid "eintr"
msgstr "Przerwane wywołanie systemowe"
msgstr ""
msgid "einval"
msgstr "Zły argument"
msgstr ""
msgid "eio"
msgstr "Błąd wejścia/wyjścia"
msgstr ""
msgid "eisdir"
msgstr "Jest katalogiem"
msgstr ""
msgid "eloop"
msgstr "Za duże zagnieżdżenie dowiązań symbolicznych"
msgstr ""
msgid "emfile"
msgstr "Za dużo otwartych plików"
msgstr ""
msgid "emlink"
msgstr "Za dużo dowiązań"
msgstr ""
msgid "emultihop"
msgstr "Multihop attempted"
msgstr ""
msgid "enametoolong"
msgstr "Za długa nazwa pliku"
msgstr ""
msgid "enfile"
msgstr "Za dużo otwartych plików w systemie"
msgstr ""
msgid "enobufs"
msgstr "Brak miejsca w buforze"
msgstr ""
msgid "enodev"
msgstr "Nie ma takiego urządzenia"
msgstr ""
msgid "enolck"
msgstr "Brak dostępnych blokad"
msgstr ""
msgid "enolink"
msgstr "Połączenie zostało przerwane"
msgstr ""
msgid "enoent"
msgstr "Nie ma takiego pliku ani katalogu"
msgstr ""
msgid "enomem"
msgstr "Nie można przydzielić pamięci"
msgstr ""
msgid "enospc"
msgstr "Brak miejsca na urządzeniu"
msgstr ""
msgid "enosr"
msgstr "Brak dodatkowych strumieni"
msgstr ""
msgid "enostr"
msgstr "Nie jest strumieniem"
msgstr ""
msgid "enosys"
msgstr "Niezaimplementowana funkcja"
msgstr ""
msgid "enotblk"
msgstr "Wymagane urządzenie blokowe"
msgstr ""
msgid "enotdir"
msgstr "Nie jest katalogiem"
msgstr ""
msgid "enotsup"
msgstr "Operacja nieobsługiwana"
msgstr ""
msgid "enxio"
msgstr "Nie ma takiego urządzenia ani adresu"
msgstr ""
msgid "eopnotsupp"
msgstr "Operacja na gnieździe nieobsługiwana"
msgstr ""
msgid "eoverflow"
msgstr "Wartość za duża dla zdefiniowanego typu danych"
msgstr ""
msgid "epipe"
msgstr "Przerwany potok"
msgstr ""
msgid "erange"
msgstr "Za duży wynik"
msgstr ""
msgid "erofs"
msgstr "System plików wyłącznie do odczytu"
msgstr ""
msgid "espipe"
msgstr "Błędne przesunięcie"
msgstr ""
msgid "esrch"
msgstr "Nie ma takiego procesu"
msgstr ""
msgid "estale"
msgstr "Nieaktualny uchwyt pliku"
msgstr ""
msgid "etxtbsy"
msgstr "Plik tekstowy zajęty"
msgstr ""
msgid "exdev"
msgstr "Niepoprawne dowiązanie"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@ -1,657 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-18 12:55+0000\n"
"PO-Revision-Date: 2024-03-19 01:10+0000\n"
"Last-Translator: Jammer Lammer <akHarINlMYExpSmVPDRT@proton.me>\n"
"Language-Team: Portuguese <http://translate.akkoma.dev/projects/akkoma/"
"akkoma-backend-errors/pt/>\n"
"Language: pt\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.18.2\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.
## From Ecto.Changeset.cast/4
msgid "can't be blank"
msgstr "não pode estar em branco"
## From Ecto.Changeset.unique_constraint/3
msgid "has already been taken"
msgstr "já está em uso"
## From Ecto.Changeset.put_change/3
msgid "is invalid"
msgstr "é inválido"
## From Ecto.Changeset.validate_format/3
msgid "has invalid format"
msgstr "tem um formato inválido"
## From Ecto.Changeset.validate_subset/3
msgid "has an invalid entry"
msgstr "tem uma entrada inválida"
## From Ecto.Changeset.validate_exclusion/3
msgid "is reserved"
msgstr "está reservado"
## From Ecto.Changeset.validate_confirmation/3
msgid "does not match confirmation"
msgstr "a confirmação não coincide"
## From Ecto.Changeset.no_assoc_constraint/3
msgid "is still associated with this entry"
msgstr "ainda está associado com essa entrada"
msgid "are still associated with this entry"
msgstr "ainda estão associados com essa entrada"
## From Ecto.Changeset.validate_length/3
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] "deve ser %{count} caractere"
msgstr[1] "deve ser %{count} caracteres"
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] "deve ter %{count} item"
msgstr[1] "deve ter %{count} itens"
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] "deve ter pelo menos %{count} caractere"
msgstr[1] "deve ter pelo menos %{count} caracteres"
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] "deve ter pelo menos %{count} item"
msgstr[1] "deve ter pelo menos %{count} itens"
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] "deve ter no máximo %{count} caractere"
msgstr[1] "deve ter no máximo %{count} caracteres"
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] "deve ter no máximo %{count} item"
msgstr[1] "deve ter no máximo %{count} itens"
## From Ecto.Changeset.validate_number/3
msgid "must be less than %{number}"
msgstr "deve ser menor que %{number}"
msgid "must be greater than %{number}"
msgstr "deve ser maior que %{number}"
msgid "must be less than or equal to %{number}"
msgstr "deve ser menor ou igual a %{number}"
msgid "must be greater than or equal to %{number}"
msgstr "deve ser maior ou igual a %{number}"
msgid "must be equal to %{number}"
msgstr "deve ser igual a %{number}"
#: lib/pleroma/web/common_api.ex:503
#, elixir-autogen, elixir-format
msgid "Account not found"
msgstr "Conta não encontrada"
#: lib/pleroma/web/common_api.ex:263
#, elixir-autogen, elixir-format
msgid "Already voted"
msgstr "Já votado"
#: lib/pleroma/web/o_auth/o_auth_controller.ex:427
#, elixir-autogen, elixir-format
msgid "Bad request"
msgstr "Má requisição"
#: lib/pleroma/web/controller_helper.ex:105
#, elixir-autogen, elixir-format
msgid "Can't display this activity"
msgstr "Não é possível mostrar essa atividade"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:335
#, elixir-autogen, elixir-format
msgid "Can't find user"
msgstr "Não é possível encontrar o usuário"
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61
#, elixir-autogen, elixir-format
msgid "Can't get favorites"
msgstr "Não é possível obter os favoritos"
#: lib/pleroma/web/common_api/utils.ex:480
#, elixir-autogen, elixir-format
msgid "Cannot post an empty status without attachments"
msgstr "Não é possível publicar um status vazio sem anexos"
#: lib/pleroma/web/common_api/utils.ex:468
#, elixir-autogen, elixir-format
msgid "Comment must be up to %{max_size} characters"
msgstr "Comentários devem ter no máximo %{max_size} caracteres"
#: lib/pleroma/config_db.ex:199
#, elixir-autogen, elixir-format
msgid "Config with params %{params} not found"
msgstr "Configuração com parâmetros %{params} não encontrada"
#: lib/pleroma/web/common_api.ex:114
#: lib/pleroma/web/common_api.ex:118
#, elixir-autogen, elixir-format
msgid "Could not delete"
msgstr "Não foi possível apagar"
#: lib/pleroma/web/common_api.ex:164
#, elixir-autogen, elixir-format
msgid "Could not favorite"
msgstr "Não foi possível favoritar"
#: lib/pleroma/web/common_api.ex:201
#, elixir-autogen, elixir-format
msgid "Could not unfavorite"
msgstr "Não foi possível eliminar favorito"
#: lib/pleroma/web/common_api.ex:149
#, elixir-autogen, elixir-format
msgid "Could not unrepeat"
msgstr "Não foi possível republicar"
#: lib/pleroma/web/common_api.ex:510
#: lib/pleroma/web/common_api.ex:519
#, elixir-autogen, elixir-format
msgid "Could not update state"
msgstr "Não foi possível atualizar estado"
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:278
#, elixir-autogen, elixir-format
msgid "Error."
msgstr "Erro."
#: lib/pleroma/web/twitter_api/twitter_api.ex:104
#, elixir-autogen, elixir-format
msgid "Invalid CAPTCHA"
msgstr "CAPTCHA inválido"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:143
#: lib/pleroma/web/o_auth/o_auth_controller.ex:660
#, elixir-autogen, elixir-format
msgid "Invalid credentials"
msgstr "Credenciais inválidas"
#: lib/pleroma/web/plugs/ensure_authenticated_plug.ex:42
#, elixir-autogen, elixir-format
msgid "Invalid credentials."
msgstr "Credenciais inválidas."
#: lib/pleroma/web/common_api.ex:284
#, elixir-autogen, elixir-format
msgid "Invalid indices"
msgstr "Índices inválidos"
#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29
#, elixir-autogen, elixir-format
msgid "Invalid parameters"
msgstr "Parâmetros inválidos"
#: lib/pleroma/web/common_api/utils.ex:376
#, elixir-autogen, elixir-format
msgid "Invalid password."
msgstr "Senha inválida."
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:265
#, elixir-autogen, elixir-format
msgid "Invalid request"
msgstr "Requisição inválida"
#: lib/pleroma/web/twitter_api/twitter_api.ex:107
#, elixir-autogen, elixir-format
msgid "Kocaptcha service unavailable"
msgstr "Serviço de Kocaptcha indisponível"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:139
#, elixir-autogen, elixir-format
msgid "Missing parameters"
msgstr "Parâmetros faltando"
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:151
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:177
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:219
#, elixir-autogen, elixir-format
msgid "No such permission_group"
msgstr "Não a tal permission_group"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:480
#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11
#: lib/pleroma/web/feed/tag_controller.ex:16
#: lib/pleroma/web/feed/user_controller.ex:70
#: lib/pleroma/web/o_status/o_status_controller.ex:135
#: lib/pleroma/web/plugs/uploaded_media.ex:83
#, elixir-autogen, elixir-format
msgid "Not found"
msgstr "Não encontrado"
#: lib/pleroma/web/common_api.ex:255
#, elixir-autogen, elixir-format
msgid "Poll's author can't vote"
msgstr "Autor da enquete não pode votar"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:478
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:39
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:51
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:52
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
#, elixir-autogen, elixir-format
msgid "Record not found"
msgstr "Registro não encontrado"
#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35
#: lib/pleroma/web/feed/user_controller.ex:79
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:42
#: lib/pleroma/web/o_status/o_status_controller.ex:141
#, elixir-autogen, elixir-format
msgid "Something went wrong"
msgstr "Algo deu errado"
#: lib/pleroma/web/common_api/activity_draft.ex:156
#, elixir-autogen, elixir-format
msgid "The message visibility must be direct"
msgstr "A visibilidade da mensagem deve ser direta"
#: lib/pleroma/web/common_api/utils.ex:490
#, elixir-autogen, elixir-format
msgid "The status is over the character limit"
msgstr "O status está acima do limite de caracteres"
#: lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex:36
#, elixir-autogen, elixir-format
msgid "This resource requires authentication."
msgstr "Esse recurso requer autenticação."
#: lib/pleroma/web/plugs/rate_limiter.ex:214
#, elixir-autogen, elixir-format
msgid "Throttled"
msgstr "Limitado"
#: lib/pleroma/web/common_api.ex:285
#, elixir-autogen, elixir-format
msgid "Too many choices"
msgstr "Muitas opções"
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:248
#, elixir-autogen, elixir-format
msgid "You can't revoke your own admin status."
msgstr "Você não pode revogar seu próprio status de administrador."
#: lib/pleroma/web/o_auth/o_auth_controller.ex:267
#: lib/pleroma/web/o_auth/o_auth_controller.ex:358
#, elixir-autogen, elixir-format
msgid "Your account is currently disabled"
msgstr "Sua conta está atualmente desativada"
#: lib/pleroma/web/o_auth/o_auth_controller.ex:229
#: lib/pleroma/web/o_auth/o_auth_controller.ex:381
#, elixir-autogen, elixir-format
msgid "Your login is missing a confirmed e-mail address"
msgstr "Sua conta não possui uma endereço de email confirmado"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:368
#, elixir-autogen, elixir-format
msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr "não é possível ler o inbox de %{nickname} como %{as_nickname}"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:467
#, elixir-autogen, elixir-format
msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr "não é possível atualizar o inbox de %{nickname} como %{nickname}"
#: lib/pleroma/web/common_api.ex:455
#, elixir-autogen, elixir-format
msgid "conversation is already muted"
msgstr "a conversa já está silenciada"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486
#, elixir-autogen, elixir-format
msgid "error"
msgstr "erro"
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:34
#, elixir-autogen, elixir-format
msgid "mascots can only be images"
msgstr "mascotes só podem ser imagens"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62
#, elixir-autogen, elixir-format
msgid "not found"
msgstr "não encontrado"
#: lib/pleroma/web/o_auth/o_auth_controller.ex:462
#, elixir-autogen, elixir-format
msgid "Bad OAuth request."
msgstr "Requisição de OAuth inválida."
#: lib/pleroma/web/twitter_api/twitter_api.ex:113
#, elixir-autogen, elixir-format
msgid "CAPTCHA already used"
msgstr "CAPTCHA já está em uso"
#: lib/pleroma/web/twitter_api/twitter_api.ex:110
#, elixir-autogen, elixir-format
msgid "CAPTCHA expired"
msgstr "CAPTCHA expirado"
#: lib/pleroma/web/plugs/uploaded_media.ex:56
#, elixir-autogen, elixir-format
msgid "Failed"
msgstr "Falhou"
#: lib/pleroma/web/o_auth/o_auth_controller.ex:478
#, elixir-autogen, elixir-format
msgid "Failed to authenticate: %{message}."
msgstr "Falha ao autenticar: %{message}."
#: lib/pleroma/web/o_auth/o_auth_controller.ex:509
#, elixir-autogen, elixir-format
msgid "Failed to set up user account."
msgstr "Falha ao definir conta de usuário."
#: lib/pleroma/web/plugs/o_auth_scopes_plug.ex:37
#, elixir-autogen, elixir-format
msgid "Insufficient permissions: %{permissions}."
msgstr "Permissões insuficientes: %{permissions}."
#: lib/pleroma/web/plugs/uploaded_media.ex:98
#, elixir-autogen, elixir-format
msgid "Internal Error"
msgstr "Erro Interno"
#: lib/pleroma/web/o_auth/fallback_controller.ex:22
#: lib/pleroma/web/o_auth/fallback_controller.ex:29
#, elixir-autogen, elixir-format
msgid "Invalid Username/Password"
msgstr "Usuário/Senha Inválidos"
#: lib/pleroma/web/twitter_api/twitter_api.ex:116
#, elixir-autogen, elixir-format
msgid "Invalid answer data"
msgstr "dado de resposta inválido"
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:40
#, elixir-autogen, elixir-format
msgid "Nodeinfo schema version not handled"
msgstr "Esquema de versão do Nodeinfo não tratado"
#: lib/pleroma/web/o_auth/fallback_controller.ex:14
#, elixir-autogen, elixir-format
msgid "Unknown error, please check the details and try again."
msgstr "Erro desconhecido. Por favor, cheque os detalhes e tente novamente."
#: lib/pleroma/web/o_auth/o_auth_controller.ex:158
#: lib/pleroma/web/o_auth/o_auth_controller.ex:204
#, elixir-autogen, elixir-format
msgid "Unlisted redirect_uri."
msgstr "redirect_uri Não listada."
#: lib/pleroma/web/o_auth/o_auth_controller.ex:458
#, elixir-autogen, elixir-format
msgid "Unsupported OAuth provider: %{provider}."
msgstr "Provedor de OAuth não suportado: %{provider}."
#: lib/pleroma/uploaders/uploader.ex:74
#, elixir-autogen, elixir-format
msgid "Uploader callback timeout"
msgstr "Tempo esgotado para callback do uploader"
#: lib/pleroma/web/uploader_controller.ex:23
#, elixir-autogen, elixir-format
msgid "bad request"
msgstr "má requisição"
#: lib/pleroma/web/twitter_api/twitter_api.ex:101
#, elixir-autogen, elixir-format
msgid "CAPTCHA Error"
msgstr "Erro no CAPTCHA"
#: lib/pleroma/web/common_api.ex:213
#, elixir-autogen, elixir-format
msgid "Could not add reaction emoji"
msgstr "Não foi possível adicionar emoji de reação"
#: lib/pleroma/web/common_api.ex:224
#, elixir-autogen, elixir-format
msgid "Could not remove reaction emoji"
msgstr "Não foi possível remover emoji de reação"
#: lib/pleroma/web/twitter_api/twitter_api.ex:127
#, elixir-autogen, elixir-format
msgid "Invalid CAPTCHA (Missing parameter: %{name})"
msgstr "CAPTCHA inválido (Parâmetro faltando: %{name})"
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:96
#, elixir-autogen, elixir-format
msgid "List not found"
msgstr "Lista não encontrada"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:150
#, elixir-autogen, elixir-format
msgid "Missing parameter: %{name}"
msgstr "Parâmetro faltando: %{name}"
#: lib/pleroma/web/o_auth/o_auth_controller.ex:256
#: lib/pleroma/web/o_auth/o_auth_controller.ex:371
#, elixir-autogen, elixir-format
msgid "Password reset is required"
msgstr "Redefinição de senha é necessária"
#: lib/pleroma/tests/auth_test_controller.ex:9
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/announcement_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/config_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/frontend_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/instance_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/instance_document_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/relay_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/status_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/user_controller.ex:6
#: lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex:2
#: lib/pleroma/web/akkoma_api/controllers/frontend_switcher.ex:2
#: lib/pleroma/web/akkoma_api/controllers/metrics_controller.ex:2
#: lib/pleroma/web/akkoma_api/controllers/translation_controller.ex:2
#: lib/pleroma/web/controller_helper.ex:6
#: lib/pleroma/web/embed_controller.ex:6
#: lib/pleroma/web/fallback/redirect_controller.ex:6
#: lib/pleroma/web/feed/tag_controller.ex:6
#: lib/pleroma/web/feed/user_controller.ex:6
#: lib/pleroma/web/mailer/subscription_controller.ex:6
#: lib/pleroma/web/manifest_controller.ex:6
#: lib/pleroma/web/masto_fe_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:11
#: lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/directory_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14
#: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/report_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7
#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:3
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6
#: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6
#: lib/pleroma/web/mongoose_im/mongoose_im_controller.ex:6
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6
#: lib/pleroma/web/o_auth/fallback_controller.ex:6
#: lib/pleroma/web/o_auth/mfa_controller.ex:10
#: lib/pleroma/web/o_auth/o_auth_controller.ex:6
#: lib/pleroma/web/o_status/o_status_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/app_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/backup_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/instances_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/report_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7
#: lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex:6
#: lib/pleroma/web/static_fe/static_fe_controller.ex:6
#: lib/pleroma/web/twitter_api/controller.ex:6
#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10
#: lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6
#: lib/pleroma/web/uploader_controller.ex:6
#: lib/pleroma/web/web_finger/web_finger_controller.ex:6
#, elixir-autogen, elixir-format
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
msgstr ""
"Violação de segurança: Verificação do escopo do OAuth tanto não lidou quanto "
"não explicitamente pulou."
#: lib/pleroma/web/plugs/ensure_authenticated_plug.ex:32
#, elixir-autogen, elixir-format
msgid "Two-factor authentication enabled, you must use a access token."
msgstr ""
"Autenticação de dois-fatores ativada, você deve usar um token de acesso."
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
#, elixir-autogen, elixir-format
msgid "Web push subscription is disabled on this Pleroma instance"
msgstr "Inscrição de web push está desativada nessa instância do Akkoma"
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:214
#, elixir-autogen, elixir-format
msgid "You can't revoke your own admin/moderator status."
msgstr "Você não pode revogar o seu próprio status de admin/moderador."
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202
#, elixir-autogen, elixir-format
msgid "authorization required for timeline view"
msgstr "autorização necessária para visualização da linha do tempo"
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24
#, elixir-autogen, elixir-format
msgid "Access denied"
msgstr "Acesso negado"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:332
#, elixir-autogen, elixir-format
msgid "This API requires an authenticated user"
msgstr "Essa API necessita de um usuário autentica"
#: lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex:26
#: lib/pleroma/web/plugs/user_is_admin_plug.ex:21
#, elixir-autogen, elixir-format
msgid "User is not an admin."
msgstr "Usuário não é um administrador."
#: lib/pleroma/user/backup.ex:73
#, elixir-format
msgid "Last export was less than a day ago"
msgid_plural "Last export was less than %{days} days ago"
msgstr[0] "Última exportação foi a menos de um dia atrás"
msgstr[1] "Última exportação foi a menos de %{days} atrás"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:399
#, elixir-autogen, elixir-format
msgid "Character limit (%{limit} characters) exceeded, contains %{length} characters"
msgstr ""
"Limite de caracteres (%{limit} caracteres) excedido, pois contém %{length} "
"caracteres"
#: lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex:33
#: lib/pleroma/web/plugs/user_is_staff_plug.ex:20
#, elixir-autogen, elixir-format
msgid "User is not a staff member."
msgstr "Usuário não é um membro da staff."
#: lib/pleroma/web/o_auth/o_auth_controller.ex:391
#, elixir-autogen, elixir-format
msgid "Your account is awaiting approval."
msgstr "Sua conta aguarda aprovação."
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:256
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:259
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:262
#, elixir-autogen, elixir-format
msgid "File is too large"
msgstr "Arquivo muito grande"
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:37
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:48
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:59
#, elixir-autogen, elixir-format
msgid "Hashtag not found"
msgstr "Hashtag não encontrada"
#: lib/pleroma/web/common_api/activity_draft.ex:144
#, elixir-autogen, elixir-format
msgid "Invalid language"
msgstr "Idioma inválido"
#: lib/pleroma/web/o_auth/o_auth_controller.ex:218
#, elixir-autogen, elixir-format
msgid "This action is outside of authorized scopes"
msgstr "Essa ação está fora do escopo autorizado"
#: lib/pleroma/web/common_api/activity_draft.ex:129
#, elixir-autogen, elixir-format
msgid "You can only quote public or unlisted statuses"
msgstr "Você pode apenas citar status públicos ou não-listados"
#: lib/pleroma/web/common_api/activity_draft.ex:126
#, elixir-autogen, elixir-format
msgid "You can't quote a status that doesn't exist"
msgstr "Você não pode citar um status que não existe"
#: lib/pleroma/web/embed_controller.ex:35
#, elixir-autogen, elixir-format
msgid "Federated posts cannot be embedded"
msgstr "Publicações federadas não podem ser embutidas"
#: lib/pleroma/web/embed_controller.ex:38
#, elixir-autogen, elixir-format
msgid "Not authorized to view this post"
msgstr "Não autorizado a ver essa publicação"
#: lib/pleroma/web/embed_controller.ex:32
#, elixir-autogen, elixir-format
msgid "Post not found"
msgstr "Publicação não encontrada"

View File

@ -1,163 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-17 22:50+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: pt\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Translate Toolkit 3.9.2\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 ""

View File

@ -1,616 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-17 22:51+0000\n"
"PO-Revision-Date: 2024-03-19 01:10+0000\n"
"Last-Translator: Jammer Lammer <akHarINlMYExpSmVPDRT@proton.me>\n"
"Language-Team: Portuguese <http://translate.akkoma.dev/projects/akkoma/"
"akkoma-backend-static-pages/pt/>\n"
"Language: pt\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.18.2\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 "Autorizar"
#: 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 "Erro ao buscar usuário"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "remote follow header"
msgid "Remote follow"
msgstr "Seguimento remoto"
#: 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 "Código de autenticação"
#: 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 "Senha"
#: 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 "Nome de usuário"
#: 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 "Autorizar"
#: 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 "Autorizar"
#: 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 "Erro ao seguir conta"
#: 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 "Inicia a sessão para seguir"
#: 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 "Autenticação de dois-fatores"
#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "remote follow success"
msgid "Account followed!"
msgstr "Conta seguida!"
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:7
#: 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 "Sua ID de conta, ex. lain@quitter.se"
#: 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 "Seguir"
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:2
#, elixir-autogen, elixir-format
msgctxt "remote follow error"
msgid "Error: %{error}"
msgstr "Erro: %{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 "Seguir remotamente %{nickname}"
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:12
#, elixir-autogen, elixir-format
msgctxt "password reset button"
msgid "Reset"
msgstr "Resetar"
#: 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 "Página inicial"
#: 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 "Falha ao redefinir senha"
#: 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 "Confirmação"
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "password reset form password prompt"
msgid "Password"
msgstr "Senha"
#: 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 "Token Inválido"
#: 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 "Página inicial"
#: 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 "Senha alterada!"
#: 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 ""
"Estas são notas públicas marcadas com #%{tag}. Você pode interagir com elas "
"se você tem uma conta em qualquer lugar no fediverse."
#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:3
#, elixir-autogen, elixir-format
msgctxt "oauth authorization exists page title"
msgid "Authorization exists"
msgstr "Existe autorização"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:37
#, elixir-autogen, elixir-format
msgctxt "oauth authorize approve button"
msgid "Approve"
msgstr "Aprovar"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:35
#, elixir-autogen, elixir-format
msgctxt "oauth authorize cancel button"
msgid "Cancel"
msgstr "Cancelar"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:26
#, elixir-autogen, elixir-format
msgctxt "oauth authorize message"
msgid "Application <strong>%{client_name}</strong> is requesting access to your account."
msgstr ""
"A aplicação <strong>%{client_name}</strong> está requisitando acesso à sua "
"conta."
#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:3
#, elixir-autogen, elixir-format
msgctxt "oauth authorized page title"
msgid "Successfully authorized"
msgstr "Autorização feita com sucesso"
#: 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 "Iniciar sessão com provedor externo"
#: 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 "Iniciar sessão com %{strategy}"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:59
#, elixir-autogen, elixir-format
msgctxt "oauth login button"
msgid "Log In"
msgstr "Iniciar sessão"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:56
#, elixir-autogen, elixir-format
msgctxt "oauth login password prompt"
msgid "Password"
msgstr "Senha"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:52
#, elixir-autogen, elixir-format
msgctxt "oauth login username prompt"
msgid "Username"
msgstr "Usuário"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:44
#, elixir-autogen, elixir-format
msgctxt "oauth register nickname prompt"
msgid "Pleroma Handle"
msgstr "Usuário Pleroma"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:42
#, 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 ""
"Escolha com cautela! Você não será capaz de mudar isso depois. No entanto, "
"você será capaz de mudar o seu nome à mostra."
#: 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 "Email"
#: 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 ""
"Se você deseja registrar uma nova conta, por favor, provenha os detalhes "
"abaixo."
#: 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 "Proceder como um usuário já existente"
#: 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 "Senha"
#: 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 "Alternativamente, inicia uma sessão em uma conta existente."
#: 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 "Nome ou email"
#: 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 "Apelido"
#: 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 "Proceder como um novo usuário"
#: 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 "Detalhes do registro"
#: 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 "As seguintes permissões serão garantidas"
#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:6
#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:6
#, elixir-autogen, elixir-format
msgctxt "oauth token code message"
msgid "Token code is <br>%{token}"
msgstr "O código do token é <br>%{token}"
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:14
#, elixir-autogen, elixir-format
msgctxt "mfa auth code prompt"
msgid "Authentication code"
msgstr "Código de autenticação"
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:9
#, elixir-autogen, elixir-format
msgctxt "mfa auth page title"
msgid "Two-factor authentication"
msgstr "Autenticação de dois-fatores"
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:25
#, elixir-autogen, elixir-format
msgctxt "mfa auth page use recovery code link"
msgid "Enter a two-factor recovery code"
msgstr "Insira um código de recuperação de dois-fatores"
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:22
#, elixir-autogen, elixir-format
msgctxt "mfa auth verify code button"
msgid "Verify"
msgstr "Verificar"
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:9
#, elixir-autogen, elixir-format
msgctxt "mfa recover page title"
msgid "Two-factor recovery"
msgstr "Recuperação de dois-fatores"
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:14
#, elixir-autogen, elixir-format
msgctxt "mfa recover recovery code prompt"
msgid "Recovery code"
msgstr "Código de recuperação"
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:25
#, elixir-autogen, elixir-format
msgctxt "mfa recover use 2fa code link"
msgid "Enter a two-factor code"
msgstr "Insira um código de dois-fatores"
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:22
#, elixir-autogen, elixir-format
msgctxt "mfa recover verify recovery code button"
msgid "Verify"
msgstr "Verificar"
#: lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex:42
#, elixir-autogen, elixir-format
msgctxt "static fe profile page remote follow button"
msgid "Remote follow"
msgstr "Seguir remotamente"
#: 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 "Ei, %{nickname}, veja o que você perdeu!"
#: 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 ""
"O email ao qual você está inscrito é <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 ""
"Você recebeu esse email porque você se inscreveu para receber resumos por "
"email da instância Akkoma <b>%{instance}</b>."
#: 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 "Para se desinscrever, por favor vá %{here}."
#: lib/pleroma/web/templates/email/digest.html.eex:547
#, elixir-autogen, elixir-format
msgctxt "digest email unsubscribe action link text"
msgid "here"
msgstr "aqui"
#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "mailer unsubscribe failed message"
msgid "UNSUBSCRIBE FAILURE"
msgstr "FALHA AO SE DESINSCREVER"
#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "mailer unsubscribe successful message"
msgid "UNSUBSCRIBE SUCCESSFUL"
msgstr "INSCRIÇÃO CANCELADA COM SUCESSO"
#: 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} Novo Seguidor"
msgstr[1] "%{count} Novos Seguidores"
#: lib/pleroma/emails/user_email.ex:384
#, elixir-autogen, elixir-format
msgctxt "account archive email subject"
msgid "Your account archive is ready"
msgstr "O arquivamento da sua conta está pronto"
#: 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> Aguardando Aprovação </h3>\n"
"<p>Sua conta na instância %{instance_name} está sendo revisada pela staff. "
"Você receberá um novo email assim que a conta for aprovada. </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 "Sua conta está aguardando aprovação"
#: 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> Obrigado por se registrar na instância %{instance_name}</h3>\n"
"<p> Uma confirmação de email é necessário para ativar a sua conta </p>\n"
"<p>Por favor, clique no seguinte link para <a href=\"%{confirmation_url}\""
">ativar a sua conta</a>.</p>\n"
#: lib/pleroma/emails/user_email.ex:174
#, elixir-autogen, elixir-format
msgctxt "confirmation email subject"
msgid "%{instance_name} account confirmation"
msgstr "Confirmação de conta de %{instance_name}"
#: lib/pleroma/emails/user_email.ex:310
#, elixir-autogen, elixir-format
msgctxt "digest email subject"
msgid "Your digest from %{instance_name}"
msgstr "O seu resumo de %{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>Redefina a sua senha em %{instance_name}</h3>\n"
"<p>Alguém requisitou uma redefinição de senha para a sua conta em "
"%{instance_name}.</p>\n"
"<p>Caso tenha sido você, visite o seguinte link para proceder: <a href=\""
"%{passoword_reset_url}\">redefinir senha</a>.</p>\n"
"<p>Se por acaso tenha sido outra pessoa, não há com o que se preocupar: seus "
"dados estão seguros e a sua senha não foi alterada.</p>\n"
#: lib/pleroma/emails/user_email.ex:98
#, elixir-autogen, elixir-format
msgctxt "password reset email subject"
msgid "Password reset"
msgstr "Senha redefinida"
#: 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>Olá, @%{nickname},</h3>\n"
"<p>Sua conta em %{instance_name} foi criada com sucesso.</p>\n"
"<p>Nenhuma ação extra é necessária para ativar a sua conta.</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 "Conta registrada em %{instance_name}"
#: lib/pleroma/emails/user_email.ex:136
#, elixir-autogen, elixir-format
msgctxt "user invitation email subject"
msgid "Invitation to %{instance_name}"
msgstr "Convite para %{instance_name}"
#: lib/pleroma/emails/user_email.ex:53
#, elixir-autogen, elixir-format
msgctxt "welcome email html body"
msgid "Welcome to %{instance_name}!"
msgstr "Bem-vindo(a) à instância %{instance_name}!"
#: lib/pleroma/emails/user_email.ex:41
#, elixir-autogen, elixir-format
msgctxt "welcome email subject"
msgid "Welcome to %{instance_name}!"
msgstr "Bem-vindo(a) à instância %{instance_name}!"
#: lib/pleroma/emails/user_email.ex:65
#, elixir-autogen, elixir-format
msgctxt "welcome email text body"
msgid "Welcome to %{instance_name}!"
msgstr "Bem-vindo(a) à instância %{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 Akkoma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
msgstr ""
"<p>O administrador @%{admin_nickname} requisitou um backup completo da sua "
"conta Akkoma. Ele está pronto para download:</p>\n"
"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
#: 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 Akkoma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
msgstr ""
"<p> Foi requisitou uma backup completo da sua conta Akkoma. Está pronto para "
"download:</p>\n"
"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:41
#, elixir-autogen, elixir-format
msgctxt "oauth register page title"
msgid "This is your first visit! Please enter your Akkoma handle."
msgstr ""
"Esta é a sua primeira visita! Por favor, entre com o seu sobrenome Akkoma."
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:123
#, elixir-autogen, elixir-format
msgctxt "remote follow error message - unknown error"
msgid "Something went wrong."
msgstr "Algo deu errado."
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:67
#, elixir-autogen, elixir-format
msgctxt "remote follow error message - user not found"
msgid "Could not find user"
msgstr "Não foi possível encontrar o usuário"
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "status interact authorization button"
msgid "Interact"
msgstr "Interagir"
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:2
#, elixir-autogen, elixir-format
msgctxt "status interact error"
msgid "Error: %{error}"
msgstr "Erro: %{error}"
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:95
#, elixir-autogen, elixir-format
msgctxt "status interact error message - status not found"
msgid "Could not find status"
msgstr "Não foi possível achar o status"
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:144
#, elixir-autogen, elixir-format
msgctxt "status interact error message - unknown error"
msgid "Something went wrong."
msgstr "Algo deu errado."
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "status interact header"
msgid "Interacting with %{nickname}'s %{status_link}"
msgstr "Interagindo com o %{status_link} de %{nickname}"
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "status interact header - status link text"
msgid "status"
msgstr "status"
#: 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 Akkoma federated social networking platform.</p>\n<p>Click the following link to register: <a href=\"%{registration_url}\">accept invitation</a>.</p>\n"
msgstr ""
"<h3> Você foi convidado(a) para a instância %{instance_name}</h3>\n"
"<p>%{inviter_name} lhe convidou a se juntar à instância %{instance_name}, "
"uma instância Akkoma da plataforma de redes sociais federadas.</p>\n"
"<p>Clique no seguinte link para se registrar: <a href=\"%{registration_url}\""
">aceitar convite</a>.</p>\n"

View File

@ -3,16 +3,16 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-06 11:13+0000\n"
"PO-Revision-Date: 2023-09-08 05:53+0000\n"
"Last-Translator: Nguyễn Gia Phong <cnx@loang.net>\n"
"Language-Team: Vietnamese <http://translate.akkoma.dev/projects/akkoma/"
"akkoma-backend-errors/vi/>\n"
"PO-Revision-Date: 2021-09-07 16:42+0000\n"
"Last-Translator: Hồ Nhất Duy <kantcer@gmail.com>\n"
"Language-Team: Vietnamese <https://translate.pleroma.social/projects/pleroma/"
"pleroma/vi/>\n"
"Language: vi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 4.18.2\n"
"X-Generator: Weblate 4.6.2\n"
## This file is a PO Template file.
##
@ -577,37 +577,37 @@ msgstr "Người này không phải quản trị viên."
#, elixir-format
msgid "Last export was less than a day ago"
msgid_plural "Last export was less than %{days} days ago"
msgstr[0] "Vừa sao lưu lần cuối %{days} ngày trước"
msgstr[0] ""
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:399
#, elixir-autogen, elixir-format
msgid "Character limit (%{limit} characters) exceeded, contains %{length} characters"
msgstr "Quá dài: %{length} kí tự (tối đa %{limit})"
msgstr ""
#: lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex:33
#: lib/pleroma/web/plugs/user_is_staff_plug.ex:20
#, elixir-autogen, elixir-format
#, elixir-autogen, elixir-format, fuzzy
msgid "User is not a staff member."
msgstr "Người dùng không phải quản trị viên."
msgstr "Người này không phải quản trị viên."
#: lib/pleroma/web/o_auth/o_auth_controller.ex:391
#, elixir-autogen, elixir-format
msgid "Your account is awaiting approval."
msgstr "Tài khoản của bạn đang chờ được duyệt."
msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:256
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:259
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:262
#, elixir-autogen, elixir-format
msgid "File is too large"
msgstr "Tệp quá lớn"
msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:37
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:48
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:59
#, elixir-autogen, elixir-format
#, elixir-autogen, elixir-format, fuzzy
msgid "Hashtag not found"
msgstr "Không tìm thấy hashtag"
msgstr "Không tìm thấy danh sách"
#: lib/pleroma/web/common_api/activity_draft.ex:144
#, elixir-autogen, elixir-format
@ -627,7 +627,7 @@ msgstr ""
#: lib/pleroma/web/common_api/activity_draft.ex:126
#, elixir-autogen, elixir-format
msgid "You can't quote a status that doesn't exist"
msgstr "Bạn không thể trích dẫn bàn viết không tồn tại"
msgstr ""
#: lib/pleroma/web/embed_controller.ex:35
#, elixir-autogen, elixir-format
@ -637,9 +637,9 @@ msgstr ""
#: lib/pleroma/web/embed_controller.ex:38
#, elixir-autogen, elixir-format
msgid "Not authorized to view this post"
msgstr "Không có quyền xem bài viết này"
msgstr ""
#: lib/pleroma/web/embed_controller.ex:32
#, elixir-autogen, elixir-format
#, elixir-autogen, elixir-format, fuzzy
msgid "Post not found"
msgstr "Không tìm thấy bài viết"
msgstr "Không tìm thấy danh sách"

View File

@ -8,589 +8,555 @@
### to merge POT files into PO files.
msgid ""
msgstr ""
"PO-Revision-Date: 2024-04-22 23:54+0000\n"
"Last-Translator: Toot <toothpicker@users.noreply.translate.akkoma.dev>\n"
"Language-Team: Chinese (Traditional) <http://translate.akkoma.dev/projects/"
"akkoma/akkoma-backend-static-pages/zh_Hant/>\n"
"Language: zh_Hant\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 4.18.2\n"
"Content-Transfer-Encoding: 8bit\n"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:9
#, elixir-autogen, elixir-format
msgctxt "remote follow authorization button"
msgid "Authorize"
msgstr "批准"
msgstr ""
#: 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 "無法獲取用戶信息"
msgstr ""
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "remote follow header"
msgid "Remote follow"
msgstr "跨站關注"
msgstr ""
#: 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 "驗證碼"
msgstr ""
#: 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 "密碼"
msgstr ""
#: 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 "用戶名"
msgstr ""
#: 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 "授權"
msgstr ""
#: 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 "授權"
msgstr ""
#: 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 "無法關注帳戶"
msgstr ""
#: 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 "登錄以關注"
msgstr ""
#: 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 "雙因素身份驗證"
msgstr ""
#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "remote follow success"
msgid "Account followed!"
msgstr "已關注該賬號!"
msgstr ""
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:7
#: 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 "你的帳號ID比如lain@quitter.se"
msgstr ""
#: 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 "關注"
msgstr ""
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:2
#, elixir-autogen, elixir-format
msgctxt "remote follow error"
msgid "Error: %{error}"
msgstr "錯誤:%{error}"
msgstr ""
#: 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}"
msgstr ""
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:12
#, elixir-autogen, elixir-format
msgctxt "password reset button"
msgid "Reset"
msgstr "重設"
msgstr ""
#: 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 "主頁"
msgstr ""
#: 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 "密碼重置失敗"
msgstr ""
#: 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 "確認"
msgstr ""
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "password reset form password prompt"
msgid "Password"
msgstr "密碼"
msgstr ""
#: 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 "無效 Token"
msgstr ""
#: 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 "主頁"
msgstr ""
#: 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 "密碼已修改!"
msgstr ""
#: 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 "這些是帶有 #%{tag} "
"標籤的公開貼文。如果你在聯邦宇宙的任何地方有帳號,你可以與它們進行互動。"
msgstr ""
#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:3
#, elixir-autogen, elixir-format
msgctxt "oauth authorization exists page title"
msgid "Authorization exists"
msgstr "已存在授權"
msgstr ""
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:37
#, elixir-autogen, elixir-format
msgctxt "oauth authorize approve button"
msgid "Approve"
msgstr "允許"
msgstr ""
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:35
#, elixir-autogen, elixir-format
msgctxt "oauth authorize cancel button"
msgid "Cancel"
msgstr "取消"
msgstr ""
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:26
#, elixir-autogen, elixir-format
msgctxt "oauth authorize message"
msgid "Application <strong>%{client_name}</strong> is requesting access to your account."
msgstr "應用程序 <strong>%{client_name}</strong> 正在請求訪問您的帳戶。"
msgstr ""
#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:3
#, elixir-autogen, elixir-format
msgctxt "oauth authorized page title"
msgid "Successfully authorized"
msgstr "授權成功"
msgstr ""
#: 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 "使用外部服務進行登錄"
msgstr ""
#: 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 "用 %{strategy} 登錄"
msgstr ""
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:59
#, elixir-autogen, elixir-format
msgctxt "oauth login button"
msgid "Log In"
msgstr "登錄"
msgstr ""
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:56
#, elixir-autogen, elixir-format
msgctxt "oauth login password prompt"
msgid "Password"
msgstr "密碼"
msgstr ""
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:52
#, elixir-autogen, elixir-format
msgctxt "oauth login username prompt"
msgid "Username"
msgstr "用戶名"
msgstr ""
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:44
#, elixir-autogen, elixir-format
msgctxt "oauth register nickname prompt"
msgid "Pleroma Handle"
msgstr "Pleroma 帳號"
msgstr ""
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:42
#, 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 "選擇時要慎重!您以後將無法更改此項。不過您可以更改您的顯示名稱。"
msgstr ""
#: 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 "郵箱"
msgstr ""
#: 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 "如果您想註冊一個新帳戶,請提供以下細節。"
msgstr ""
#: 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 "以現有用戶身份進行"
msgstr ""
#: 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 "密碼"
msgstr ""
#: 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 "或者登錄後連接到現有賬戶。"
msgstr ""
#: 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 "名字或者郵箱"
msgstr ""
#: 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 "暱稱"
msgstr ""
#: 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 "以新用戶身份進行"
msgstr ""
#: 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 "註冊細節"
msgstr ""
#: 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 "將授予以下權限"
msgstr ""
#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:6
#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:6
#, elixir-autogen, elixir-format
msgctxt "oauth token code message"
msgid "Token code is <br>%{token}"
msgstr "Token 碼是 <br>%{token}"
msgstr ""
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:14
#, elixir-autogen, elixir-format
msgctxt "mfa auth code prompt"
msgid "Authentication code"
msgstr "授權碼"
msgstr ""
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:9
#, elixir-autogen, elixir-format
msgctxt "mfa auth page title"
msgid "Two-factor authentication"
msgstr "雙因素身份驗證"
msgstr ""
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:25
#, elixir-autogen, elixir-format
msgctxt "mfa auth page use recovery code link"
msgid "Enter a two-factor recovery code"
msgstr "輸入一個雙因素恢復的恢復代碼"
msgstr ""
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:22
#, elixir-autogen, elixir-format
msgctxt "mfa auth verify code button"
msgid "Verify"
msgstr "認證"
msgstr ""
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:9
#, elixir-autogen, elixir-format
msgctxt "mfa recover page title"
msgid "Two-factor recovery"
msgstr "雙因素恢復"
msgstr ""
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:14
#, elixir-autogen, elixir-format
msgctxt "mfa recover recovery code prompt"
msgid "Recovery code"
msgstr "恢復碼"
msgstr ""
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:25
#, elixir-autogen, elixir-format
msgctxt "mfa recover use 2fa code link"
msgid "Enter a two-factor code"
msgstr "輸入一個雙重因素驗證碼"
msgstr ""
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:22
#, elixir-autogen, elixir-format
msgctxt "mfa recover verify recovery code button"
msgid "Verify"
msgstr "驗證"
msgstr ""
#: lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex:42
#, elixir-autogen, elixir-format
msgctxt "static fe profile page remote follow button"
msgid "Remote follow"
msgstr "跨站關注"
msgstr ""
#: 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 "嗨 %{nickname},這是你錯過了的一些東西!"
msgstr ""
#: 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 ""
"您訂閱的電子郵件地址是 <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 "您之所以會收到來自 <b>%{instance}</b> Akkoma "
"實例的郵件摘要,是因爲您已經註冊了該服務實例。"
msgstr ""
#: 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 "取消訂閱,請點擊 %{here}."
msgstr ""
#: lib/pleroma/web/templates/email/digest.html.eex:547
#, elixir-autogen, elixir-format
msgctxt "digest email unsubscribe action link text"
msgid "here"
msgstr "這里"
msgstr ""
#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "mailer unsubscribe failed message"
msgid "UNSUBSCRIBE FAILURE"
msgstr "取消訂閱失敗"
msgstr ""
#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "mailer unsubscribe successful message"
msgid "UNSUBSCRIBE SUCCESSFUL"
msgstr "成功取消訂閱"
msgstr ""
#: 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} 個新關注者"
msgstr[0] ""
#: lib/pleroma/emails/user_email.ex:384
#, elixir-autogen, elixir-format
msgctxt "account archive email subject"
msgid "Your account archive is ready"
msgstr "您的帳戶檔案已經準備好了"
msgstr ""
#: 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>正在等待批准</h3>\n"
"<p> 您在 %{instance_name} 的帳戶正在被工作人員審查。一旦您的帳戶被批准通過,"
"您將收到另一封電子郵件。</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 "您的帳戶正在等待審批"
msgstr ""
#: 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>感謝註冊 %{instance_name}</h3>\n"
"<p>需要電子郵件確認才能激活該帳戶</p>\n"
"<p>請點擊以下鏈結以 <a href=\"%{confirmation_url}\">確認您的帳戶</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} 帳戶確認"
msgstr ""
#: lib/pleroma/emails/user_email.ex:310
#, elixir-autogen, elixir-format
msgctxt "digest email subject"
msgid "Your digest from %{instance_name}"
msgstr "您來自 %{instance_name} 的摘要郵件"
msgstr ""
#: 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>在 %{instance_name} 重置您的密碼</h3>\n"
"<p>有人請求更改您在 %{instance_name} 的帳戶密碼。</p>\n"
"<p>如果這是您的操作,請點擊以下鏈結繼續重置密碼:<a href=\""
"%{password_reset_url}\">重置密碼</a>。</p>\n"
"<p>如果這不是您的操作,請不用擔心:您的數據是安全的,您的密碼未被更改。</p>\n"
#: lib/pleroma/emails/user_email.ex:98
#, elixir-autogen, elixir-format
msgctxt "password reset email subject"
msgid "Password reset"
msgstr "重置密碼"
msgstr ""
#: 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>你好,@%{nickname}</h3>\n"
"<p>你在 %{instance_name} 的帳戶已經成功註冊。</p>\n"
"<p>無需進行其他操作即可激活你的帳戶。</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 "帳號註冊在 %{instance_name}"
msgstr ""
#: lib/pleroma/emails/user_email.ex:136
#, elixir-autogen, elixir-format
msgctxt "user invitation email subject"
msgid "Invitation to %{instance_name}"
msgstr "邀請加入 %{instance_name}"
msgstr ""
#: lib/pleroma/emails/user_email.ex:53
#, elixir-autogen, elixir-format
msgctxt "welcome email html body"
msgid "Welcome to %{instance_name}!"
msgstr "歡迎來到 %{instance_name}"
msgstr ""
#: lib/pleroma/emails/user_email.ex:41
#, elixir-autogen, elixir-format
msgctxt "welcome email subject"
msgid "Welcome to %{instance_name}!"
msgstr "歡迎來到 %{instance_name}"
msgstr ""
#: lib/pleroma/emails/user_email.ex:65
#, elixir-autogen, elixir-format
msgctxt "welcome email text body"
msgid "Welcome to %{instance_name}!"
msgstr "歡迎來到 %{instance_name}"
msgstr ""
#: 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 Akkoma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
msgstr ""
"<p>管理員 @%{admin_nickname} 請求對你的 Akkoma "
"帳戶進行完整備份,備份已準備好可供下載:</p>\n"
"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
#: 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 Akkoma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
msgstr ""
"<p>您請求了 Akkoma 帳戶的完整備份。備份已準備就緒,可以下載:</p>\n"
"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:41
#, elixir-autogen, elixir-format
msgctxt "oauth register page title"
msgid "This is your first visit! Please enter your Akkoma handle."
msgstr "這是您的第一次訪問!請填寫您的 Akkoma 帳號。"
msgstr ""
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:123
#, elixir-autogen, elixir-format
msgctxt "remote follow error message - unknown error"
msgid "Something went wrong."
msgstr "發生了一些錯誤。"
msgstr ""
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:67
#, elixir-autogen, elixir-format
msgctxt "remote follow error message - user not found"
msgid "Could not find user"
msgstr "無法找到相應用戶"
msgstr ""
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "status interact authorization button"
msgid "Interact"
msgstr "互動"
msgstr ""
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:2
#, elixir-autogen, elixir-format
msgctxt "status interact error"
msgid "Error: %{error}"
msgstr "錯誤:%{error}"
msgstr ""
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:95
#, elixir-autogen, elixir-format
msgctxt "status interact error message - status not found"
msgid "Could not find status"
msgstr "無法找到貼文"
msgstr ""
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:144
#, elixir-autogen, elixir-format
msgctxt "status interact error message - unknown error"
msgid "Something went wrong."
msgstr "發生了一些錯誤。"
msgstr ""
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "status interact header"
msgid "Interacting with %{nickname}'s %{status_link}"
msgstr "與 %{nickname} 的 %{status_link} 進行交互"
msgstr ""
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "status interact header - status link text"
msgid "status"
msgstr "貼文"
msgstr ""
#: 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 Akkoma federated social networking platform.</p>\n<p>Click the following link to register: <a href=\"%{registration_url}\">accept invitation</a>.</p>\n"
msgstr ""
"<h3>您被邀請加入 %{instance_name}</h3>\n"
"<p>%{inviter_name} 邀請您加入 %{instance_name},這是一個使用 Akkoma "
"聯邦社交網絡平臺的實例。</p>\n"
"<p>點擊以下鏈結註冊: <a href=\"%{registration_url}\">接受邀請</a>.</p>\n"

View File

@ -1,37 +0,0 @@
defmodule Pleroma.Repo.Migrations.UploadFilterExiftoolToExiftoolStripMetadata do
use Ecto.Migration
alias Pleroma.ConfigDB
def up,
do:
ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Upload})
|> update_filtername(
Pleroma.Upload.Filter.Exiftool,
Pleroma.Upload.Filter.Exiftool.StripMetadata
)
def down,
do:
ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Upload})
|> update_filtername(
Pleroma.Upload.Filter.Exiftool.StripMetadata,
Pleroma.Upload.Filter.Exiftool
)
defp update_filtername(%{value: value}, from_filtername, to_filtername) do
new_value =
value
|> Keyword.update(:filters, [], fn filters ->
filters
|> Enum.map(fn
^from_filtername -> to_filtername
filter -> filter
end)
end)
ConfigDB.update_or_create(%{group: :pleroma, key: Pleroma.Upload, value: new_value})
end
defp update_filtername(_, _, _), do: nil
end

View File

@ -19,7 +19,6 @@
"toot": "http://joinmastodon.org/ns#",
"misskey": "https://misskey-hub.net/ns#",
"fedibird": "http://fedibird.com/ns#",
"sharkey": "https://joinsharkey.org/ns#",
"value": "schema:value",
"sensitive": "as:sensitive",
"litepub": "http://litepub.social/ns#",
@ -46,14 +45,6 @@
"contentMap": {
"@id": "as:content",
"@container": "@language"
},
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"backgroundUrl": {
"@id": "sharkey:backgroundUrl",
"@type": "@id"
}
}
]

View File

@ -117,7 +117,6 @@ nav {
.inner-nav img {
height: 28px;
width: auto;
vertical-align: middle;
padding-right: 5px
}
@ -441,7 +440,6 @@ summary {
.avatar img {
border-radius: 3px;
box-shadow: var(--avatarShadow);
object-fit: cover;
}
.user-summary {
@ -644,13 +642,13 @@ summary {
}
img:not(.u-photo, .fa-icon) {
width: auto;
width: 32px;
height: 32px;
padding: 0;
vertical-align: middle;
}
.username img:not(.u-photo) {
width: auto;
width: 16px;
height: 16px;
}

View File

@ -78,8 +78,6 @@ config :joken, default_signer: "<%= jwt_secret %>"
config :pleroma, configurable_from_database: <%= db_configurable? %>
config :pleroma, Pleroma.Upload,
<%= if Kernel.length(upload_filters) > 0 do
" filters: #{inspect(upload_filters)},"
"config :pleroma, Pleroma.Upload, filters: #{inspect(upload_filters)}"
end %>
base_url: "<%= media_url %>"

View File

@ -9,9 +9,3 @@
## Tweak GC to run more often
##-env ERL_FULLSWEEP_AFTER 10
## Disable busy waits; vastly reduces CPU usage while idle
## See https://docs.akkoma.dev/stable/configuration/optimisation/optimizing_beam/#virtual-machine-andor-few-cpu-cores
+sbwt none
+sbwtdcpu none
+sbwtdio none

View File

@ -70,7 +70,7 @@
"preferredUsername": "jk.nipponalba.scot",
"summary": "",
"publicKey": {
"id": "https://fed.brid.gy/jk.nipponalba.scot#key",
"id": "jk.nipponalba.scot",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdarxwzxnNbJ2hneWOYHkYJowk\npyigQtxlUd0VjgSQHwxU9kWqfbrHBVADyTtcqi/4dAzQd3UnCI1TPNnn4LPZY9PW\noiWd3Zl1/EfLFxO7LU9GS7fcSLQkyj5JNhSlN3I8QPudZbybrgRDVZYooDe1D+52\n5KLGqC2ajrIVOiDRTQIDAQAB\n-----END PUBLIC KEY-----"
},
"inbox": "https://fed.brid.gy/jk.nipponalba.scot/inbox",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 823 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 631 B

View File

@ -1,117 +0,0 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#",
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"featuredTags": {
"@id": "toot:featuredTags",
"@type": "@id"
},
"alsoKnownAs": {
"@id": "as:alsoKnownAs",
"@type": "@id"
},
"movedTo": {
"@id": "as:movedTo",
"@type": "@id"
},
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value",
"discoverable": "toot:discoverable",
"Device": "toot:Device",
"Ed25519Signature": "toot:Ed25519Signature",
"Ed25519Key": "toot:Ed25519Key",
"Curve25519Key": "toot:Curve25519Key",
"EncryptedMessage": "toot:EncryptedMessage",
"publicKeyBase64": "toot:publicKeyBase64",
"deviceId": "toot:deviceId",
"claim": {
"@type": "@id",
"@id": "toot:claim"
},
"fingerprintKey": {
"@type": "@id",
"@id": "toot:fingerprintKey"
},
"identityKey": {
"@type": "@id",
"@id": "toot:identityKey"
},
"devices": {
"@type": "@id",
"@id": "toot:devices"
},
"messageFranking": "toot:messageFranking",
"messageType": "toot:messageType",
"cipherText": "toot:cipherText",
"suspended": "toot:suspended",
"memorial": "toot:memorial",
"indexable": "toot:indexable",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
}
}
],
"id": "https://mastodont.cat/users/fediverse",
"type": "Service",
"following": "https://mastodont.cat/users/fediverse/following",
"followers": "https://mastodont.cat/users/fediverse/followers",
"inbox": "https://mastodont.cat/users/fediverse/inbox",
"outbox": "https://mastodont.cat/users/fediverse/outbox",
"featured": "https://mastodont.cat/users/fediverse/collections/featured",
"featuredTags": "https://mastodont.cat/users/fediverse/collections/tags",
"preferredUsername": "fediverse",
"name": "fediverse's stats",
"summary": "<p>All fediverse alive servers stats. New refactored code!</p><p>Ask server info:</p><p><span class=\"h-card\" translate=\"no\"><a href=\"https://mastodont.cat/@fediverse\" class=\"u-url mention\">@<span>fediverse</span></a></span> server example.server</p><p>Ask software info:</p><p><span class=\"h-card\" translate=\"no\"><a href=\"https://mastodont.cat/@fediverse\" class=\"u-url mention\">@<span>fediverse</span></a></span> soft mastodon</p>",
"url": "https://mastodont.cat/@fediverse",
"manuallyApprovesFollowers": false,
"discoverable": true,
"indexable": false,
"published": "2020-05-13T00:00:00Z",
"memorial": false,
"devices": "https://mastodont.cat/users/fediverse/collections/devices",
"publicKey": {
"id": "https://mastodont.cat/users/fediverse#main-key",
"owner": "https://mastodont.cat/users/fediverse",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu2X8LqAR/6j95UUTG02T\nWG+PmNRWnfOl+zjDts3OctyJK7at5AwA+T0be1faHpf+oLREl/dkWXc8VQY2UJzY\n8QTuXXnIkwHAeA7WADB6kPvQhVpfGPgKD0dpAgBz9WHFquMSXcnuyt7q1CDn5wId\nRoUtkCAcg1rOX+lIAoeic5hT0O0sXLJdtaSCTZmGqkF2Cf+/16q8XhRevMRh73vP\nX2PefCr63Iy/Zh5rnVhPluQMyQ6FGxXgd5dEKJRa2kxrhIsrm0TzMX892Ev45AwI\ndppYQOQ+nLOgMYrpFNYdOmizJsn635l18K1r/tyDDAegPp6Kfa8v+BaZdOmNTFKr\n/wIDAQAB\n-----END PUBLIC KEY-----\n"
},
"tag": [],
"attachment": [
{
"type": "PropertyValue",
"name": "code",
"value": "<a href=\"https://codeberg.org/spla/stats\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">codeberg.org/spla/stats</span><span class=\"invisible\"></span></a>"
},
{
"type": "PropertyValue",
"name": "my user-agent",
"value": "&quot;fediverse&#39;s stats (fediverse@mastodont.cat)&quot;"
},
{
"type": "PropertyValue",
"name": "coded by",
"value": "<span class=\"h-card\" translate=\"no\"><a href=\"https://mastodont.cat/@spla\" class=\"u-url mention\">@<span>spla</span></a></span>"
}
],
"endpoints": {
"sharedInbox": "https://mastodont.cat/inbox"
},
"icon": {
"type": "Image",
"mediaType": "image/png",
"url": "https://mastodont.cat/system/accounts/avatars/000/149/323/original/33201dbeb139a24a.png"
},
"image": {
"type": "Image",
"mediaType": "image/jpeg",
"url": "https://mastodont.cat/system/accounts/headers/000/149/323/original/75c861d59e5a8860.jpeg"
}
}

Some files were not shown because too many files have changed in this diff Show More