Merge remote-tracking branch 'remotes/origin/develop' into 2168-media-preview-proxy
This commit is contained in:
commit
6fd4f58ead
|
@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- **Breaking:** removed `with_move` parameter from notifications timeline.
|
||||
|
||||
### Added
|
||||
- Instance: Add `background_image` to configuration and `/api/v1/instance`
|
||||
- Instance: Extend `/api/v1/instance` with Pleroma-specific information.
|
||||
- NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list.
|
||||
- NodeInfo: `pleroma_emoji_reactions` to the `features` list.
|
||||
|
|
|
@ -123,7 +123,7 @@ def generate_tagged_activities(opts \\ []) do
|
|||
Enum.each(1..activity_count, fn _ ->
|
||||
random = :rand.uniform()
|
||||
i = Enum.find_index(intervals, fn {lower, upper} -> lower <= random && upper > random end)
|
||||
CommonAPI.post(Enum.random(users), %{"status" => "a post with the tag #tag_#{i}"})
|
||||
CommonAPI.post(Enum.random(users), %{status: "a post with the tag #tag_#{i}"})
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -137,8 +137,8 @@ defp generate_long_thread(visibility, user, friends, non_friends, _opts) do
|
|||
|
||||
{:ok, activity} =
|
||||
CommonAPI.post(user, %{
|
||||
"status" => "Start of #{visibility} long thread",
|
||||
"visibility" => visibility
|
||||
status: "Start of #{visibility} long thread",
|
||||
visibility: visibility
|
||||
})
|
||||
|
||||
Agent.update(:benchmark_state, fn state ->
|
||||
|
@ -186,7 +186,7 @@ defp insert_activity("simple", visibility, group, user, friends, non_friends, _o
|
|||
{:ok, _activity} =
|
||||
group
|
||||
|> get_actor(user, friends, non_friends)
|
||||
|> CommonAPI.post(%{"status" => "Simple status", "visibility" => visibility})
|
||||
|> CommonAPI.post(%{status: "Simple status", visibility: visibility})
|
||||
end
|
||||
|
||||
defp insert_activity("emoji", visibility, group, user, friends, non_friends, _opts) do
|
||||
|
@ -194,8 +194,8 @@ defp insert_activity("emoji", visibility, group, user, friends, non_friends, _op
|
|||
group
|
||||
|> get_actor(user, friends, non_friends)
|
||||
|> CommonAPI.post(%{
|
||||
"status" => "Simple status with emoji :firefox:",
|
||||
"visibility" => visibility
|
||||
status: "Simple status with emoji :firefox:",
|
||||
visibility: visibility
|
||||
})
|
||||
end
|
||||
|
||||
|
@ -213,8 +213,8 @@ defp insert_activity("mentions", visibility, group, user, friends, non_friends,
|
|||
group
|
||||
|> get_actor(user, friends, non_friends)
|
||||
|> CommonAPI.post(%{
|
||||
"status" => Enum.join(user_mentions, ", ") <> " simple status with mentions",
|
||||
"visibility" => visibility
|
||||
status: Enum.join(user_mentions, ", ") <> " simple status with mentions",
|
||||
visibility: visibility
|
||||
})
|
||||
end
|
||||
|
||||
|
@ -236,8 +236,8 @@ defp insert_activity("hell_thread", visibility, group, user, friends, non_friend
|
|||
group
|
||||
|> get_actor(user, friends, non_friends)
|
||||
|> CommonAPI.post(%{
|
||||
"status" => mentions <> " hell thread status",
|
||||
"visibility" => visibility
|
||||
status: mentions <> " hell thread status",
|
||||
visibility: visibility
|
||||
})
|
||||
end
|
||||
|
||||
|
@ -262,9 +262,9 @@ defp insert_activity("attachment", visibility, group, user, friends, non_friends
|
|||
|
||||
{:ok, _activity} =
|
||||
CommonAPI.post(actor, %{
|
||||
"status" => "Post with attachment",
|
||||
"visibility" => visibility,
|
||||
"media_ids" => [object.id]
|
||||
status: "Post with attachment",
|
||||
visibility: visibility,
|
||||
media_ids: [object.id]
|
||||
})
|
||||
end
|
||||
|
||||
|
@ -272,7 +272,7 @@ defp insert_activity("tag", visibility, group, user, friends, non_friends, _opts
|
|||
{:ok, _activity} =
|
||||
group
|
||||
|> get_actor(user, friends, non_friends)
|
||||
|> CommonAPI.post(%{"status" => "Status with #tag", "visibility" => visibility})
|
||||
|> CommonAPI.post(%{status: "Status with #tag", visibility: visibility})
|
||||
end
|
||||
|
||||
defp insert_activity("like", visibility, group, user, friends, non_friends, opts) do
|
||||
|
@ -312,8 +312,7 @@ defp insert_activity("simple_thread", visibility, group, user, friends, non_frie
|
|||
actor = get_actor(group, user, friends, non_friends)
|
||||
tasks = get_reply_tasks(visibility, group)
|
||||
|
||||
{:ok, activity} =
|
||||
CommonAPI.post(user, %{"status" => "Simple status", "visibility" => visibility})
|
||||
{:ok, activity} = CommonAPI.post(user, %{status: "Simple status", visibility: visibility})
|
||||
|
||||
acc = {activity.id, ["@" <> actor.nickname, "reply to status"]}
|
||||
insert_replies(tasks, visibility, user, friends, non_friends, acc)
|
||||
|
@ -336,8 +335,8 @@ defp insert_activity("simple_thread", "direct", group, user, friends, non_friend
|
|||
|
||||
{:ok, activity} =
|
||||
CommonAPI.post(actor, %{
|
||||
"status" => Enum.join(data, ", ") <> "simple status",
|
||||
"visibility" => "direct"
|
||||
status: Enum.join(data, ", ") <> "simple status",
|
||||
visibility: "direct"
|
||||
})
|
||||
|
||||
acc = {activity.id, ["@" <> user.nickname | data] ++ ["reply to status"]}
|
||||
|
@ -527,9 +526,9 @@ defp insert_direct_replies(tasks, user, list, acc) do
|
|||
defp insert_reply(actor, data, activity_id, visibility) do
|
||||
{:ok, reply} =
|
||||
CommonAPI.post(actor, %{
|
||||
"status" => Enum.join(data, ", "),
|
||||
"visibility" => visibility,
|
||||
"in_reply_to_status_id" => activity_id
|
||||
status: Enum.join(data, ", "),
|
||||
visibility: visibility,
|
||||
in_reply_to_status_id: activity_id
|
||||
})
|
||||
|
||||
{reply.id, ["@" <> actor.nickname | data]}
|
||||
|
|
|
@ -387,56 +387,47 @@ defp render_timelines(user) do
|
|||
|
||||
favourites = ActivityPub.fetch_favourites(user)
|
||||
|
||||
output_relationships =
|
||||
!!Pleroma.Config.get([:extensions, :output_relationships_in_statuses_by_default])
|
||||
|
||||
Benchee.run(
|
||||
%{
|
||||
"Rendering home timeline" => fn ->
|
||||
StatusView.render("index.json", %{
|
||||
activities: home_activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: !output_relationships
|
||||
as: :activity
|
||||
})
|
||||
end,
|
||||
"Rendering direct timeline" => fn ->
|
||||
StatusView.render("index.json", %{
|
||||
activities: direct_activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: !output_relationships
|
||||
as: :activity
|
||||
})
|
||||
end,
|
||||
"Rendering public timeline" => fn ->
|
||||
StatusView.render("index.json", %{
|
||||
activities: public_activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: !output_relationships
|
||||
as: :activity
|
||||
})
|
||||
end,
|
||||
"Rendering tag timeline" => fn ->
|
||||
StatusView.render("index.json", %{
|
||||
activities: tag_activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: !output_relationships
|
||||
as: :activity
|
||||
})
|
||||
end,
|
||||
"Rendering notifications" => fn ->
|
||||
Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{
|
||||
notifications: notifications,
|
||||
for: user,
|
||||
skip_relationships: !output_relationships
|
||||
for: user
|
||||
})
|
||||
end,
|
||||
"Rendering favourites timeline" => fn ->
|
||||
StatusView.render("index.json", %{
|
||||
activities: favourites,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: !output_relationships
|
||||
as: :activity
|
||||
})
|
||||
end
|
||||
},
|
||||
|
|
|
@ -183,6 +183,7 @@
|
|||
email: "example@example.com",
|
||||
notify_email: "noreply@example.com",
|
||||
description: "A Pleroma instance, an alternative fediverse server",
|
||||
background_image: "/images/city.jpg",
|
||||
limit: 5_000,
|
||||
chat_limit: 5_000,
|
||||
remote_limit: 100_000,
|
||||
|
@ -251,8 +252,6 @@
|
|||
]
|
||||
]
|
||||
|
||||
config :pleroma, :extensions, output_relationships_in_statuses_by_default: true
|
||||
|
||||
config :pleroma, :feed,
|
||||
post_title: %{
|
||||
max_length: 100,
|
||||
|
@ -378,6 +377,10 @@
|
|||
|
||||
config :pleroma, :media_proxy,
|
||||
enabled: false,
|
||||
invalidation: [
|
||||
enabled: false,
|
||||
provider: Pleroma.Web.MediaProxy.Invalidation.Script
|
||||
],
|
||||
proxy_opts: [
|
||||
redirect_on_failure: false,
|
||||
max_body_length: 25 * 1_048_576,
|
||||
|
|
|
@ -679,15 +679,6 @@
|
|||
7
|
||||
]
|
||||
},
|
||||
%{
|
||||
key: :federation_publisher_modules,
|
||||
type: {:list, :module},
|
||||
description:
|
||||
"List of modules for federation publishing. Module names are shortened (removed leading `Pleroma.Web.` part), but on adding custom module you need to use full name.",
|
||||
suggestions: [
|
||||
Pleroma.Web.ActivityPub.Publisher
|
||||
]
|
||||
},
|
||||
%{
|
||||
key: :allow_relay,
|
||||
type: :boolean,
|
||||
|
@ -1902,12 +1893,6 @@
|
|||
(see https://github.com/sorentwo/oban/issues/52).
|
||||
""",
|
||||
children: [
|
||||
%{
|
||||
key: :repo,
|
||||
type: :module,
|
||||
description: "Application's Ecto repo",
|
||||
suggestions: [Pleroma.Repo]
|
||||
},
|
||||
%{
|
||||
key: :verbose,
|
||||
type: {:dropdown, :atom},
|
||||
|
@ -2682,18 +2667,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :http_signatures,
|
||||
type: :group,
|
||||
description: "HTTP Signatures settings",
|
||||
children: [
|
||||
%{
|
||||
key: :adapter,
|
||||
type: :module,
|
||||
suggestions: [Pleroma.Signature]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: :http,
|
||||
|
|
|
@ -216,6 +216,7 @@ Has theses additional parameters (which are the same as in Pleroma-API):
|
|||
- `avatar_upload_limit`: The same for avatars
|
||||
- `background_upload_limit`: The same for backgrounds
|
||||
- `banner_upload_limit`: The same for banners
|
||||
- `background_image`: A background image that frontends can use
|
||||
- `pleroma.metadata.features`: A list of supported features
|
||||
- `pleroma.metadata.federation`: The federation restrictions of this instance
|
||||
- `vapid_public_key`: The public key needed for push messages
|
||||
|
|
|
@ -265,7 +265,7 @@ See [Admin-API](admin_api.md)
|
|||
* Method `PUT`
|
||||
* Authentication: required
|
||||
* Params:
|
||||
* `image`: Multipart image
|
||||
* `file`: Multipart image
|
||||
* Response: JSON. Returns a mastodon media attachment entity
|
||||
when successful, otherwise returns HTTP 415 `{"error": "error_msg"}`
|
||||
* Example response:
|
||||
|
@ -426,7 +426,7 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
|
|||
* Authentication: required
|
||||
* Params:
|
||||
* `file`: file needs to be uploaded with the multipart request or link to remote file.
|
||||
* `shortcode`: (*optional*) shortcode for new emoji, must be uniq for all emoji. If not sended, shortcode will be taken from original filename.
|
||||
* `shortcode`: (*optional*) shortcode for new emoji, must be unique for all emoji. If not sended, shortcode will be taken from original filename.
|
||||
* `filename`: (*optional*) new emoji file name. If not specified will be taken from original filename.
|
||||
* Response: JSON, list of files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message.
|
||||
|
||||
|
|
|
@ -249,6 +249,40 @@ This section describe PWA manifest instance-specific values. Currently this opti
|
|||
* `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 domains to bypass the mediaproxy
|
||||
* `invalidation`: options for remove media from cache after delete object:
|
||||
* `enabled`: Enables purge cache
|
||||
* `provider`: Which one of the [purge cache strategy](#purge-cache-strategy) to use.
|
||||
|
||||
### Purge cache strategy
|
||||
|
||||
#### Pleroma.Web.MediaProxy.Invalidation.Script
|
||||
|
||||
This strategy allow perform external bash script to purge cache.
|
||||
Urls of attachments pass to script as arguments.
|
||||
|
||||
* `script_path`: path to external script.
|
||||
|
||||
Example:
|
||||
```elixir
|
||||
config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script,
|
||||
script_path: "./installation/nginx-cache-purge.example"
|
||||
```
|
||||
|
||||
#### Pleroma.Web.MediaProxy.Invalidation.Http
|
||||
|
||||
This strategy allow perform custom http request to purge cache.
|
||||
|
||||
* `method`: http method. default is `purge`
|
||||
* `headers`: http headers. default is empty
|
||||
* `options`: request options. default is empty
|
||||
|
||||
Example:
|
||||
```elixir
|
||||
config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Http,
|
||||
method: :purge,
|
||||
headers: [],
|
||||
options: []
|
||||
```
|
||||
|
||||
## Link previews
|
||||
|
||||
|
@ -619,24 +653,6 @@ config :pleroma, :workers,
|
|||
* `enabled: false` corresponds to `config :pleroma, :workers, retries: [federator_outgoing: 1]`
|
||||
* deprecated options: `max_jobs`, `initial_timeout`
|
||||
|
||||
### Pleroma.Scheduler
|
||||
|
||||
Configuration for [Quantum](https://github.com/quantum-elixir/quantum-core) jobs scheduler.
|
||||
|
||||
See [Quantum readme](https://github.com/quantum-elixir/quantum-core#usage) for the list of supported options.
|
||||
|
||||
Example:
|
||||
|
||||
```elixir
|
||||
config :pleroma, Pleroma.Scheduler,
|
||||
global: true,
|
||||
overlap: true,
|
||||
timezone: :utc,
|
||||
jobs: [{"0 */6 * * * *", {Pleroma.Web.Websub, :refresh_subscriptions, []}}]
|
||||
```
|
||||
|
||||
The above example defines a single job which invokes `Pleroma.Web.Websub.refresh_subscriptions()` every 6 hours ("0 */6 * * * *", [crontab format](https://en.wikipedia.org/wiki/Cron)).
|
||||
|
||||
## :web_push_encryption, :vapid_details
|
||||
|
||||
Web Push Notifications configuration. You can use the mix task `mix web_push.gen.keypair` to generate it.
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# Storing Remote Media
|
||||
|
||||
Pleroma does not store remote/federated media by default. The best way to achieve this is to change Nginx to keep its reverse proxy cache
|
||||
for a year and to activate the `MediaProxyWarmingPolicy` MRF policy in Pleroma which will automatically fetch all media through the proxy
|
||||
as soon as the post is received by your instance.
|
||||
|
||||
## Nginx
|
||||
|
||||
```
|
||||
proxy_cache_path /long/term/storage/path/pleroma-media-cache levels=1:2
|
||||
keys_zone=pleroma_media_cache:10m inactive=1y use_temp_path=off;
|
||||
|
||||
location ~ ^/(media|proxy) {
|
||||
proxy_cache pleroma_media_cache;
|
||||
slice 1m;
|
||||
proxy_cache_key $host$uri$is_args$args$slice_range;
|
||||
proxy_set_header Range $slice_range;
|
||||
proxy_http_version 1.1;
|
||||
proxy_cache_valid 206 301 302 304 1h;
|
||||
proxy_cache_valid 200 1y;
|
||||
proxy_cache_use_stale error timeout invalid_header updating;
|
||||
proxy_ignore_client_abort on;
|
||||
proxy_buffering on;
|
||||
chunked_transfer_encoding on;
|
||||
proxy_ignore_headers Cache-Control Expires;
|
||||
proxy_hide_header Cache-Control Expires;
|
||||
proxy_pass http://127.0.0.1:4000;
|
||||
}
|
||||
```
|
||||
|
||||
## Pleroma
|
||||
|
||||
Add to your `prod.secret.exs`:
|
||||
|
||||
```
|
||||
config :pleroma, :instance,
|
||||
rewrite_policy: [Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy]
|
||||
```
|
|
@ -0,0 +1,40 @@
|
|||
#!/bin/sh
|
||||
|
||||
# A simple shell script to delete a media from the Nginx cache.
|
||||
|
||||
SCRIPTNAME=${0##*/}
|
||||
|
||||
# NGINX cache directory
|
||||
CACHE_DIRECTORY="/tmp/pleroma-media-cache"
|
||||
|
||||
## Return the files where the items are cached.
|
||||
## $1 - the filename, can be a pattern .
|
||||
## $2 - the cache directory.
|
||||
## $3 - (optional) the number of parallel processes to run for grep.
|
||||
get_cache_files() {
|
||||
local max_parallel=${3-16}
|
||||
find $2 -maxdepth 2 -type d | xargs -P $max_parallel -n 1 grep -E Rl "^KEY:.*$1" | sort -u
|
||||
}
|
||||
|
||||
## Removes an item from the given cache zone.
|
||||
## $1 - the filename, can be a pattern .
|
||||
## $2 - the cache directory.
|
||||
purge_item() {
|
||||
for f in $(get_cache_files $1 $2); do
|
||||
echo "found file: $f"
|
||||
[ -f $f ] || continue
|
||||
echo "Deleting $f from $2."
|
||||
rm $f
|
||||
done
|
||||
} # purge_item
|
||||
|
||||
purge() {
|
||||
for url in "$@"
|
||||
do
|
||||
echo "$SCRIPTNAME delete \`$url\` from cache ($CACHE_DIRECTORY)"
|
||||
purge_item $url $CACHE_DIRECTORY
|
||||
done
|
||||
|
||||
}
|
||||
|
||||
purge $1
|
|
@ -67,8 +67,7 @@ def run(["render_timeline", nickname | _] = args) do
|
|||
Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: true
|
||||
as: :activity
|
||||
})
|
||||
end
|
||||
},
|
||||
|
|
|
@ -16,162 +16,78 @@ defmodule Pleroma.Emoji.Pack do
|
|||
|
||||
alias Pleroma.Emoji
|
||||
|
||||
@spec emoji_path() :: Path.t()
|
||||
def emoji_path do
|
||||
static = Pleroma.Config.get!([:instance, :static_dir])
|
||||
Path.join(static, "emoji")
|
||||
end
|
||||
|
||||
@spec create(String.t()) :: :ok | {:error, File.posix()} | {:error, :empty_values}
|
||||
def create(name) when byte_size(name) > 0 do
|
||||
dir = Path.join(emoji_path(), name)
|
||||
|
||||
with :ok <- File.mkdir(dir) do
|
||||
%__MODULE__{
|
||||
pack_file: Path.join(dir, "pack.json")
|
||||
}
|
||||
def create(name) do
|
||||
with :ok <- validate_not_empty([name]),
|
||||
dir <- Path.join(emoji_path(), name),
|
||||
:ok <- File.mkdir(dir) do
|
||||
%__MODULE__{pack_file: Path.join(dir, "pack.json")}
|
||||
|> save_pack()
|
||||
end
|
||||
end
|
||||
|
||||
def create(_), do: {:error, :empty_values}
|
||||
|
||||
@spec show(String.t()) :: {:ok, t()} | {:loaded, nil} | {:error, :empty_values}
|
||||
def show(name) when byte_size(name) > 0 do
|
||||
with {_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)},
|
||||
{_, pack} <- validate_pack(pack) do
|
||||
{:ok, pack}
|
||||
@spec show(String.t()) :: {:ok, t()} | {:error, atom()}
|
||||
def show(name) do
|
||||
with :ok <- validate_not_empty([name]),
|
||||
{:ok, pack} <- load_pack(name) do
|
||||
{:ok, validate_pack(pack)}
|
||||
end
|
||||
end
|
||||
|
||||
def show(_), do: {:error, :empty_values}
|
||||
|
||||
@spec delete(String.t()) ::
|
||||
{:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values}
|
||||
def delete(name) when byte_size(name) > 0 do
|
||||
emoji_path()
|
||||
|> Path.join(name)
|
||||
|> File.rm_rf()
|
||||
end
|
||||
|
||||
def delete(_), do: {:error, :empty_values}
|
||||
|
||||
@spec add_file(String.t(), String.t(), Path.t(), Plug.Upload.t() | String.t()) ::
|
||||
{:ok, t()} | {:error, File.posix()} | {:error, :empty_values}
|
||||
def add_file(name, shortcode, filename, file)
|
||||
when byte_size(name) > 0 and byte_size(shortcode) > 0 and byte_size(filename) > 0 do
|
||||
with {_, nil} <- {:exists, Emoji.get(shortcode)},
|
||||
{_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)} do
|
||||
file_path = Path.join(pack.path, filename)
|
||||
|
||||
create_subdirs(file_path)
|
||||
|
||||
case file do
|
||||
%Plug.Upload{path: upload_path} ->
|
||||
# Copy the uploaded file from the temporary directory
|
||||
File.copy!(upload_path, file_path)
|
||||
|
||||
url when is_binary(url) ->
|
||||
# Download and write the file
|
||||
file_contents = Tesla.get!(url).body
|
||||
File.write!(file_path, file_contents)
|
||||
end
|
||||
|
||||
files = Map.put(pack.files, shortcode, filename)
|
||||
|
||||
updated_pack = %{pack | files: files}
|
||||
|
||||
case save_pack(updated_pack) do
|
||||
:ok ->
|
||||
Emoji.reload()
|
||||
{:ok, updated_pack}
|
||||
|
||||
e ->
|
||||
e
|
||||
end
|
||||
def delete(name) do
|
||||
with :ok <- validate_not_empty([name]) do
|
||||
emoji_path()
|
||||
|> Path.join(name)
|
||||
|> File.rm_rf()
|
||||
end
|
||||
end
|
||||
|
||||
def add_file(_, _, _, _), do: {:error, :empty_values}
|
||||
|
||||
defp create_subdirs(file_path) do
|
||||
if String.contains?(file_path, "/") do
|
||||
file_path
|
||||
|> Path.dirname()
|
||||
|> File.mkdir_p!()
|
||||
@spec add_file(String.t(), String.t(), Path.t(), Plug.Upload.t() | String.t()) ::
|
||||
{:ok, t()} | {:error, File.posix() | atom()}
|
||||
def add_file(name, shortcode, filename, file) do
|
||||
with :ok <- validate_not_empty([name, shortcode, filename]),
|
||||
:ok <- validate_emoji_not_exists(shortcode),
|
||||
{:ok, pack} <- load_pack(name),
|
||||
:ok <- save_file(file, pack, filename),
|
||||
{:ok, updated_pack} <- pack |> put_emoji(shortcode, filename) |> save_pack() do
|
||||
Emoji.reload()
|
||||
{:ok, updated_pack}
|
||||
end
|
||||
end
|
||||
|
||||
@spec delete_file(String.t(), String.t()) ::
|
||||
{:ok, t()} | {:error, File.posix()} | {:error, :empty_values}
|
||||
def delete_file(name, shortcode) when byte_size(name) > 0 and byte_size(shortcode) > 0 do
|
||||
with {_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)},
|
||||
{_, {filename, files}} when not is_nil(filename) <-
|
||||
{:exists, Map.pop(pack.files, shortcode)},
|
||||
emoji <- Path.join(pack.path, filename),
|
||||
{_, true} <- {:exists, File.exists?(emoji)} do
|
||||
emoji_dir = Path.dirname(emoji)
|
||||
|
||||
File.rm!(emoji)
|
||||
|
||||
if String.contains?(filename, "/") and File.ls!(emoji_dir) == [] do
|
||||
File.rmdir!(emoji_dir)
|
||||
end
|
||||
|
||||
updated_pack = %{pack | files: files}
|
||||
|
||||
case save_pack(updated_pack) do
|
||||
:ok ->
|
||||
Emoji.reload()
|
||||
{:ok, updated_pack}
|
||||
|
||||
e ->
|
||||
e
|
||||
end
|
||||
{:ok, t()} | {:error, File.posix() | atom()}
|
||||
def delete_file(name, shortcode) do
|
||||
with :ok <- validate_not_empty([name, shortcode]),
|
||||
{:ok, pack} <- load_pack(name),
|
||||
:ok <- remove_file(pack, shortcode),
|
||||
{:ok, updated_pack} <- pack |> delete_emoji(shortcode) |> save_pack() do
|
||||
Emoji.reload()
|
||||
{:ok, updated_pack}
|
||||
end
|
||||
end
|
||||
|
||||
def delete_file(_, _), do: {:error, :empty_values}
|
||||
|
||||
@spec update_file(String.t(), String.t(), String.t(), String.t(), boolean()) ::
|
||||
{:ok, t()} | {:error, File.posix()} | {:error, :empty_values}
|
||||
def update_file(name, shortcode, new_shortcode, new_filename, force)
|
||||
when byte_size(name) > 0 and byte_size(shortcode) > 0 and byte_size(new_shortcode) > 0 and
|
||||
byte_size(new_filename) > 0 do
|
||||
with {_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)},
|
||||
{_, {filename, files}} when not is_nil(filename) <-
|
||||
{:exists, Map.pop(pack.files, shortcode)},
|
||||
{_, true} <- {:not_used, force or is_nil(Emoji.get(new_shortcode))} do
|
||||
old_path = Path.join(pack.path, filename)
|
||||
old_dir = Path.dirname(old_path)
|
||||
new_path = Path.join(pack.path, new_filename)
|
||||
|
||||
create_subdirs(new_path)
|
||||
|
||||
:ok = File.rename(old_path, new_path)
|
||||
|
||||
if String.contains?(filename, "/") and File.ls!(old_dir) == [] do
|
||||
File.rmdir!(old_dir)
|
||||
end
|
||||
|
||||
files = Map.put(files, new_shortcode, new_filename)
|
||||
|
||||
updated_pack = %{pack | files: files}
|
||||
|
||||
case save_pack(updated_pack) do
|
||||
:ok ->
|
||||
Emoji.reload()
|
||||
{:ok, updated_pack}
|
||||
|
||||
e ->
|
||||
e
|
||||
end
|
||||
{:ok, t()} | {:error, File.posix() | atom()}
|
||||
def update_file(name, shortcode, new_shortcode, new_filename, force) do
|
||||
with :ok <- validate_not_empty([name, shortcode, new_shortcode, new_filename]),
|
||||
{:ok, pack} <- load_pack(name),
|
||||
{:ok, filename} <- get_filename(pack, shortcode),
|
||||
:ok <- validate_emoji_not_exists(new_shortcode, force),
|
||||
:ok <- rename_file(pack, filename, new_filename),
|
||||
{:ok, updated_pack} <-
|
||||
pack
|
||||
|> delete_emoji(shortcode)
|
||||
|> put_emoji(new_shortcode, new_filename)
|
||||
|> save_pack() do
|
||||
Emoji.reload()
|
||||
{:ok, updated_pack}
|
||||
end
|
||||
end
|
||||
|
||||
def update_file(_, _, _, _, _), do: {:error, :empty_values}
|
||||
|
||||
@spec import_from_filesystem() :: {:ok, [String.t()]} | {:error, atom()}
|
||||
@spec import_from_filesystem() :: {:ok, [String.t()]} | {:error, File.posix() | atom()}
|
||||
def import_from_filesystem do
|
||||
emoji_path = emoji_path()
|
||||
|
||||
|
@ -184,7 +100,7 @@ def import_from_filesystem do
|
|||
File.dir?(path) and File.exists?(Path.join(path, "pack.json"))
|
||||
end)
|
||||
|> Enum.map(&write_pack_contents/1)
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|
||||
{:ok, names}
|
||||
else
|
||||
|
@ -193,6 +109,117 @@ def import_from_filesystem do
|
|||
end
|
||||
end
|
||||
|
||||
@spec list_remote(String.t()) :: {:ok, map()} | {:error, atom()}
|
||||
def list_remote(url) do
|
||||
uri = url |> String.trim() |> URI.parse()
|
||||
|
||||
with :ok <- validate_shareable_packs_available(uri) do
|
||||
uri
|
||||
|> URI.merge("/api/pleroma/emoji/packs")
|
||||
|> http_get()
|
||||
end
|
||||
end
|
||||
|
||||
@spec list_local() :: {:ok, map()}
|
||||
def list_local do
|
||||
with {:ok, results} <- list_packs_dir() do
|
||||
packs =
|
||||
results
|
||||
|> Enum.map(fn name ->
|
||||
case load_pack(name) do
|
||||
{:ok, pack} -> pack
|
||||
_ -> nil
|
||||
end
|
||||
end)
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|> Map.new(fn pack -> {pack.name, validate_pack(pack)} end)
|
||||
|
||||
{:ok, packs}
|
||||
end
|
||||
end
|
||||
|
||||
@spec get_archive(String.t()) :: {:ok, binary()} | {:error, atom()}
|
||||
def get_archive(name) do
|
||||
with {:ok, pack} <- load_pack(name),
|
||||
:ok <- validate_downloadable(pack) do
|
||||
{:ok, fetch_archive(pack)}
|
||||
end
|
||||
end
|
||||
|
||||
@spec download(String.t(), String.t(), String.t()) :: :ok | {:error, atom()}
|
||||
def download(name, url, as) do
|
||||
uri = url |> String.trim() |> URI.parse()
|
||||
|
||||
with :ok <- validate_shareable_packs_available(uri),
|
||||
{:ok, remote_pack} <- uri |> URI.merge("/api/pleroma/emoji/packs/#{name}") |> http_get(),
|
||||
{:ok, %{sha: sha, url: url} = pack_info} <- fetch_pack_info(remote_pack, uri, name),
|
||||
{:ok, archive} <- download_archive(url, sha),
|
||||
pack <- copy_as(remote_pack, as || name),
|
||||
{:ok, _} = unzip(archive, pack_info, remote_pack, pack) do
|
||||
# Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256
|
||||
# in it to depend on itself
|
||||
if pack_info[:fallback] do
|
||||
save_pack(pack)
|
||||
else
|
||||
{:ok, pack}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec save_metadata(map(), t()) :: {:ok, t()} | {:error, File.posix()}
|
||||
def save_metadata(metadata, %__MODULE__{} = pack) do
|
||||
pack
|
||||
|> Map.put(:pack, metadata)
|
||||
|> save_pack()
|
||||
end
|
||||
|
||||
@spec update_metadata(String.t(), map()) :: {:ok, t()} | {:error, File.posix()}
|
||||
def update_metadata(name, data) do
|
||||
with {:ok, pack} <- load_pack(name) do
|
||||
if fallback_sha_changed?(pack, data) do
|
||||
update_sha_and_save_metadata(pack, data)
|
||||
else
|
||||
save_metadata(data, pack)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec load_pack(String.t()) :: {:ok, t()} | {:error, :not_found}
|
||||
def load_pack(name) do
|
||||
pack_file = Path.join([emoji_path(), name, "pack.json"])
|
||||
|
||||
if File.exists?(pack_file) do
|
||||
pack =
|
||||
pack_file
|
||||
|> File.read!()
|
||||
|> from_json()
|
||||
|> Map.put(:pack_file, pack_file)
|
||||
|> Map.put(:path, Path.dirname(pack_file))
|
||||
|> Map.put(:name, name)
|
||||
|
||||
{:ok, pack}
|
||||
else
|
||||
{:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
@spec emoji_path() :: Path.t()
|
||||
defp emoji_path do
|
||||
[:instance, :static_dir]
|
||||
|> Pleroma.Config.get!()
|
||||
|> Path.join("emoji")
|
||||
end
|
||||
|
||||
defp validate_emoji_not_exists(shortcode, force \\ false)
|
||||
defp validate_emoji_not_exists(_shortcode, true), do: :ok
|
||||
|
||||
defp validate_emoji_not_exists(shortcode, _) do
|
||||
case Emoji.get(shortcode) do
|
||||
nil -> :ok
|
||||
_ -> {:error, :already_exists}
|
||||
end
|
||||
end
|
||||
|
||||
defp write_pack_contents(path) do
|
||||
pack = %__MODULE__{
|
||||
files: files_from_path(path),
|
||||
|
@ -201,7 +228,7 @@ defp write_pack_contents(path) do
|
|||
}
|
||||
|
||||
case save_pack(pack) do
|
||||
:ok -> Path.basename(path)
|
||||
{:ok, _pack} -> Path.basename(path)
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
@ -216,7 +243,8 @@ defp files_from_path(path) do
|
|||
# FIXME: Copy-pasted from Pleroma.Emoji/load_from_file_stream/2
|
||||
|
||||
# Create a map of shortcodes to filenames from emoji.txt
|
||||
File.read!(txt_path)
|
||||
txt_path
|
||||
|> File.read!()
|
||||
|> String.split("\n")
|
||||
|> Enum.map(&String.trim/1)
|
||||
|> Enum.map(fn line ->
|
||||
|
@ -226,21 +254,18 @@ defp files_from_path(path) do
|
|||
[name, file | _] ->
|
||||
file_dir_name = Path.dirname(file)
|
||||
|
||||
file =
|
||||
if String.ends_with?(path, file_dir_name) do
|
||||
Path.basename(file)
|
||||
else
|
||||
file
|
||||
end
|
||||
|
||||
{name, file}
|
||||
if String.ends_with?(path, file_dir_name) do
|
||||
{name, Path.basename(file)}
|
||||
else
|
||||
{name, file}
|
||||
end
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.into(%{})
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|> Map.new()
|
||||
else
|
||||
# If there's no emoji.txt, assume all files
|
||||
# that are of certain extensions from the config are emojis and import them all
|
||||
|
@ -249,60 +274,20 @@ defp files_from_path(path) do
|
|||
end
|
||||
end
|
||||
|
||||
@spec list_remote(String.t()) :: {:ok, map()}
|
||||
def list_remote(url) do
|
||||
uri =
|
||||
url
|
||||
|> String.trim()
|
||||
|> URI.parse()
|
||||
|
||||
with {_, true} <- {:shareable, shareable_packs_available?(uri)} do
|
||||
packs =
|
||||
uri
|
||||
|> URI.merge("/api/pleroma/emoji/packs")
|
||||
|> to_string()
|
||||
|> Tesla.get!()
|
||||
|> Map.get(:body)
|
||||
|> Jason.decode!()
|
||||
|
||||
{:ok, packs}
|
||||
end
|
||||
end
|
||||
|
||||
@spec list_local() :: {:ok, map()}
|
||||
def list_local do
|
||||
emoji_path = emoji_path()
|
||||
|
||||
# Create the directory first if it does not exist. This is probably the first request made
|
||||
# with the API so it should be sufficient
|
||||
with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_path)},
|
||||
{:ls, {:ok, results}} <- {:ls, File.ls(emoji_path)} do
|
||||
packs =
|
||||
results
|
||||
|> Enum.map(&load_pack/1)
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.map(&validate_pack/1)
|
||||
|> Map.new()
|
||||
|
||||
{:ok, packs}
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_pack(pack) do
|
||||
if downloadable?(pack) do
|
||||
archive = fetch_archive(pack)
|
||||
archive_sha = :crypto.hash(:sha256, archive) |> Base.encode16()
|
||||
info =
|
||||
if downloadable?(pack) do
|
||||
archive = fetch_archive(pack)
|
||||
archive_sha = :crypto.hash(:sha256, archive) |> Base.encode16()
|
||||
|
||||
info =
|
||||
pack.pack
|
||||
|> Map.put("can-download", true)
|
||||
|> Map.put("download-sha256", archive_sha)
|
||||
else
|
||||
Map.put(pack.pack, "can-download", false)
|
||||
end
|
||||
|
||||
{pack.name, Map.put(pack, :pack, info)}
|
||||
else
|
||||
info = Map.put(pack.pack, "can-download", false)
|
||||
{pack.name, Map.put(pack, :pack, info)}
|
||||
end
|
||||
Map.put(pack, :pack, info)
|
||||
end
|
||||
|
||||
defp downloadable?(pack) do
|
||||
|
@ -315,26 +300,6 @@ defp downloadable?(pack) do
|
|||
end)
|
||||
end
|
||||
|
||||
@spec get_archive(String.t()) :: {:ok, binary()}
|
||||
def get_archive(name) do
|
||||
with {_, %__MODULE__{} = pack} <- {:exists?, load_pack(name)},
|
||||
{_, true} <- {:can_download?, downloadable?(pack)} do
|
||||
{:ok, fetch_archive(pack)}
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch_archive(pack) do
|
||||
hash = :crypto.hash(:md5, File.read!(pack.pack_file))
|
||||
|
||||
case Cachex.get!(:emoji_packs_cache, pack.name) do
|
||||
%{hash: ^hash, pack_data: archive} ->
|
||||
archive
|
||||
|
||||
_ ->
|
||||
create_archive_and_cache(pack, hash)
|
||||
end
|
||||
end
|
||||
|
||||
defp create_archive_and_cache(pack, hash) do
|
||||
files = ['pack.json' | Enum.map(pack.files, fn {_, file} -> to_charlist(file) end)]
|
||||
|
||||
|
@ -356,152 +321,221 @@ defp create_archive_and_cache(pack, hash) do
|
|||
result
|
||||
end
|
||||
|
||||
@spec download(String.t(), String.t(), String.t()) :: :ok
|
||||
def download(name, url, as) do
|
||||
uri =
|
||||
url
|
||||
|> String.trim()
|
||||
|> URI.parse()
|
||||
|
||||
with {_, true} <- {:shareable, shareable_packs_available?(uri)} do
|
||||
remote_pack =
|
||||
uri
|
||||
|> URI.merge("/api/pleroma/emoji/packs/#{name}")
|
||||
|> to_string()
|
||||
|> Tesla.get!()
|
||||
|> Map.get(:body)
|
||||
|> Jason.decode!()
|
||||
|
||||
result =
|
||||
case remote_pack["pack"] do
|
||||
%{"share-files" => true, "can-download" => true, "download-sha256" => sha} ->
|
||||
{:ok,
|
||||
%{
|
||||
sha: sha,
|
||||
url: URI.merge(uri, "/api/pleroma/emoji/packs/#{name}/archive") |> to_string()
|
||||
}}
|
||||
|
||||
%{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) ->
|
||||
{:ok,
|
||||
%{
|
||||
sha: sha,
|
||||
url: src,
|
||||
fallback: true
|
||||
}}
|
||||
|
||||
_ ->
|
||||
{:error,
|
||||
"The pack was not set as shared and there is no fallback src to download from"}
|
||||
end
|
||||
|
||||
with {:ok, %{sha: sha, url: url} = pinfo} <- result,
|
||||
%{body: archive} <- Tesla.get!(url),
|
||||
{_, true} <- {:checksum, Base.decode16!(sha) == :crypto.hash(:sha256, archive)} do
|
||||
local_name = as || name
|
||||
|
||||
path = Path.join(emoji_path(), local_name)
|
||||
|
||||
pack = %__MODULE__{
|
||||
name: local_name,
|
||||
path: path,
|
||||
files: remote_pack["files"],
|
||||
pack_file: Path.join(path, "pack.json")
|
||||
}
|
||||
|
||||
File.mkdir_p!(pack.path)
|
||||
|
||||
files = Enum.map(remote_pack["files"], fn {_, path} -> to_charlist(path) end)
|
||||
# Fallback cannot contain a pack.json file
|
||||
files = if pinfo[:fallback], do: files, else: ['pack.json' | files]
|
||||
|
||||
{:ok, _} = :zip.unzip(archive, cwd: to_charlist(pack.path), file_list: files)
|
||||
|
||||
# Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256
|
||||
# in it to depend on itself
|
||||
if pinfo[:fallback] do
|
||||
save_pack(pack)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp save_pack(pack), do: File.write(pack.pack_file, Jason.encode!(pack, pretty: true))
|
||||
|
||||
@spec save_metadata(map(), t()) :: {:ok, t()} | {:error, File.posix()}
|
||||
def save_metadata(metadata, %__MODULE__{} = pack) do
|
||||
pack = Map.put(pack, :pack, metadata)
|
||||
|
||||
with :ok <- save_pack(pack) do
|
||||
defp save_pack(pack) do
|
||||
with {:ok, json} <- Jason.encode(pack, pretty: true),
|
||||
:ok <- File.write(pack.pack_file, json) do
|
||||
{:ok, pack}
|
||||
end
|
||||
end
|
||||
|
||||
@spec update_metadata(String.t(), map()) :: {:ok, t()} | {:error, File.posix()}
|
||||
def update_metadata(name, data) do
|
||||
pack = load_pack(name)
|
||||
|
||||
fb_sha_changed? =
|
||||
not is_nil(data["fallback-src"]) and data["fallback-src"] != pack.pack["fallback-src"]
|
||||
|
||||
with {_, true} <- {:update?, fb_sha_changed?},
|
||||
{:ok, %{body: zip}} <- Tesla.get(data["fallback-src"]),
|
||||
{:ok, f_list} <- :zip.unzip(zip, [:memory]),
|
||||
{_, true} <- {:has_all_files?, has_all_files?(pack.files, f_list)} do
|
||||
fallback_sha = :crypto.hash(:sha256, zip) |> Base.encode16()
|
||||
|
||||
data
|
||||
|> Map.put("fallback-src-sha256", fallback_sha)
|
||||
|> save_metadata(pack)
|
||||
else
|
||||
{:update?, _} -> save_metadata(data, pack)
|
||||
e -> e
|
||||
end
|
||||
end
|
||||
|
||||
# Check if all files from the pack.json are in the archive
|
||||
defp has_all_files?(files, f_list) do
|
||||
Enum.all?(files, fn {_, from_manifest} ->
|
||||
List.keyfind(f_list, to_charlist(from_manifest), 0)
|
||||
end)
|
||||
end
|
||||
|
||||
@spec load_pack(String.t()) :: t() | nil
|
||||
def load_pack(name) do
|
||||
pack_file = Path.join([emoji_path(), name, "pack.json"])
|
||||
|
||||
if File.exists?(pack_file) do
|
||||
pack_file
|
||||
|> File.read!()
|
||||
|> from_json()
|
||||
|> Map.put(:pack_file, pack_file)
|
||||
|> Map.put(:path, Path.dirname(pack_file))
|
||||
|> Map.put(:name, name)
|
||||
end
|
||||
end
|
||||
|
||||
defp from_json(json) do
|
||||
map = Jason.decode!(json)
|
||||
|
||||
struct(__MODULE__, %{files: map["files"], pack: map["pack"]})
|
||||
end
|
||||
|
||||
defp shareable_packs_available?(uri) do
|
||||
uri
|
||||
|> URI.merge("/.well-known/nodeinfo")
|
||||
|> to_string()
|
||||
|> Tesla.get!()
|
||||
|> Map.get(:body)
|
||||
|> Jason.decode!()
|
||||
|> Map.get("links")
|
||||
|> List.last()
|
||||
|> Map.get("href")
|
||||
# Get the actual nodeinfo address and fetch it
|
||||
|> Tesla.get!()
|
||||
|> Map.get(:body)
|
||||
|> Jason.decode!()
|
||||
|> get_in(["metadata", "features"])
|
||||
|> Enum.member?("shareable_emoji_packs")
|
||||
defp validate_shareable_packs_available(uri) do
|
||||
with {:ok, %{"links" => links}} <- uri |> URI.merge("/.well-known/nodeinfo") |> http_get(),
|
||||
# Get the actual nodeinfo address and fetch it
|
||||
{:ok, %{"metadata" => %{"features" => features}}} <-
|
||||
links |> List.last() |> Map.get("href") |> http_get() do
|
||||
if Enum.member?(features, "shareable_emoji_packs") do
|
||||
:ok
|
||||
else
|
||||
{:error, :not_shareable}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_not_empty(list) do
|
||||
if Enum.all?(list, fn i -> is_binary(i) and i != "" end) do
|
||||
:ok
|
||||
else
|
||||
{:error, :empty_values}
|
||||
end
|
||||
end
|
||||
|
||||
defp save_file(file, pack, filename) do
|
||||
file_path = Path.join(pack.path, filename)
|
||||
create_subdirs(file_path)
|
||||
|
||||
case file do
|
||||
%Plug.Upload{path: upload_path} ->
|
||||
# Copy the uploaded file from the temporary directory
|
||||
with {:ok, _} <- File.copy(upload_path, file_path), do: :ok
|
||||
|
||||
url when is_binary(url) ->
|
||||
# Download and write the file
|
||||
file_contents = Tesla.get!(url).body
|
||||
File.write(file_path, file_contents)
|
||||
end
|
||||
end
|
||||
|
||||
defp put_emoji(pack, shortcode, filename) do
|
||||
files = Map.put(pack.files, shortcode, filename)
|
||||
%{pack | files: files}
|
||||
end
|
||||
|
||||
defp delete_emoji(pack, shortcode) do
|
||||
files = Map.delete(pack.files, shortcode)
|
||||
%{pack | files: files}
|
||||
end
|
||||
|
||||
defp rename_file(pack, filename, new_filename) do
|
||||
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
|
||||
remove_dir_if_empty(old_path, filename)
|
||||
end
|
||||
end
|
||||
|
||||
defp create_subdirs(file_path) do
|
||||
if String.contains?(file_path, "/") do
|
||||
file_path
|
||||
|> Path.dirname()
|
||||
|> File.mkdir_p!()
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_file(pack, shortcode) do
|
||||
with {:ok, filename} <- get_filename(pack, shortcode),
|
||||
emoji <- Path.join(pack.path, filename),
|
||||
:ok <- File.rm(emoji) do
|
||||
remove_dir_if_empty(emoji, filename)
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_dir_if_empty(emoji, filename) do
|
||||
dir = Path.dirname(emoji)
|
||||
|
||||
if String.contains?(filename, "/") and File.ls!(dir) == [] do
|
||||
File.rmdir!(dir)
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp get_filename(pack, shortcode) do
|
||||
with %{^shortcode => filename} when is_binary(filename) <- pack.files,
|
||||
true <- pack.path |> Path.join(filename) |> File.exists?() do
|
||||
{:ok, filename}
|
||||
else
|
||||
_ -> {:error, :doesnt_exist}
|
||||
end
|
||||
end
|
||||
|
||||
defp http_get(%URI{} = url), do: url |> to_string() |> http_get()
|
||||
|
||||
defp http_get(url) do
|
||||
with {:ok, %{body: body}} <- url |> Pleroma.HTTP.get() do
|
||||
Jason.decode(body)
|
||||
end
|
||||
end
|
||||
|
||||
defp list_packs_dir do
|
||||
emoji_path = emoji_path()
|
||||
# Create the directory first if it does not exist. This is probably the first request made
|
||||
# with the API so it should be sufficient
|
||||
with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_path)},
|
||||
{:ls, {:ok, results}} <- {:ls, File.ls(emoji_path)} do
|
||||
{:ok, results}
|
||||
else
|
||||
{:create_dir, {:error, e}} -> {:error, :create_dir, e}
|
||||
{:ls, {:error, e}} -> {:error, :ls, e}
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_downloadable(pack) do
|
||||
if downloadable?(pack), do: :ok, else: {:error, :cant_download}
|
||||
end
|
||||
|
||||
defp copy_as(remote_pack, local_name) do
|
||||
path = Path.join(emoji_path(), local_name)
|
||||
|
||||
%__MODULE__{
|
||||
name: local_name,
|
||||
path: path,
|
||||
files: remote_pack["files"],
|
||||
pack_file: Path.join(path, "pack.json")
|
||||
}
|
||||
end
|
||||
|
||||
defp unzip(archive, pack_info, remote_pack, local_pack) do
|
||||
with :ok <- File.mkdir_p!(local_pack.path) do
|
||||
files = Enum.map(remote_pack["files"], fn {_, path} -> to_charlist(path) end)
|
||||
# Fallback cannot contain a pack.json file
|
||||
files = if pack_info[:fallback], do: files, else: ['pack.json' | files]
|
||||
|
||||
:zip.unzip(archive, cwd: to_charlist(local_pack.path), file_list: files)
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch_pack_info(remote_pack, uri, name) do
|
||||
case remote_pack["pack"] do
|
||||
%{"share-files" => true, "can-download" => true, "download-sha256" => sha} ->
|
||||
{:ok,
|
||||
%{
|
||||
sha: sha,
|
||||
url: URI.merge(uri, "/api/pleroma/emoji/packs/#{name}/archive") |> to_string()
|
||||
}}
|
||||
|
||||
%{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) ->
|
||||
{:ok,
|
||||
%{
|
||||
sha: sha,
|
||||
url: src,
|
||||
fallback: true
|
||||
}}
|
||||
|
||||
_ ->
|
||||
{:error, "The pack was not set as shared and there is no fallback src to download from"}
|
||||
end
|
||||
end
|
||||
|
||||
defp download_archive(url, sha) do
|
||||
with {:ok, %{body: archive}} <- Tesla.get(url) do
|
||||
if Base.decode16!(sha) == :crypto.hash(:sha256, archive) do
|
||||
{:ok, archive}
|
||||
else
|
||||
{:error, :imvalid_checksum}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch_archive(pack) do
|
||||
hash = :crypto.hash(:md5, File.read!(pack.pack_file))
|
||||
|
||||
case Cachex.get!(:emoji_packs_cache, pack.name) do
|
||||
%{hash: ^hash, pack_data: archive} -> archive
|
||||
_ -> create_archive_and_cache(pack, hash)
|
||||
end
|
||||
end
|
||||
|
||||
defp fallback_sha_changed?(pack, data) do
|
||||
is_binary(data[:"fallback-src"]) and data[:"fallback-src"] != pack.pack["fallback-src"]
|
||||
end
|
||||
|
||||
defp update_sha_and_save_metadata(pack, data) do
|
||||
with {:ok, %{body: zip}} <- Tesla.get(data[:"fallback-src"]),
|
||||
:ok <- validate_has_all_files(pack, zip) do
|
||||
fallback_sha = :sha256 |> :crypto.hash(zip) |> Base.encode16()
|
||||
|
||||
data
|
||||
|> Map.put("fallback-src-sha256", fallback_sha)
|
||||
|> save_metadata(pack)
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_has_all_files(pack, zip) do
|
||||
with {:ok, f_list} <- :zip.unzip(zip, [:memory]) do
|
||||
# Check if all files from the pack.json are in the archive
|
||||
pack.files
|
||||
|> Enum.all?(fn {_, from_manifest} ->
|
||||
List.keyfind(f_list, to_charlist(from_manifest), 0)
|
||||
end)
|
||||
|> if(do: :ok, else: {:error, :incomplete})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.MFA do
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.MFA.BackupCodes do
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.MFA.Changeset do
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.MFA.Settings do
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.MFA.Token do
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.MFA.TOTP do
|
||||
|
|
|
@ -9,11 +9,13 @@ defmodule Pleroma.Object do
|
|||
import Ecto.Changeset
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Fetcher
|
||||
alias Pleroma.ObjectTombstone
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Workers.AttachmentsCleanupWorker
|
||||
|
||||
require Logger
|
||||
|
||||
|
@ -138,12 +140,17 @@ def normalize(ap_id, true, options) when is_binary(ap_id) do
|
|||
|
||||
def normalize(_, _, _), do: nil
|
||||
|
||||
# Owned objects can only be mutated by their owner
|
||||
def authorize_mutation(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}),
|
||||
do: actor == ap_id
|
||||
# Owned objects can only be accessed by their owner
|
||||
def authorize_access(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}) do
|
||||
if actor == ap_id do
|
||||
:ok
|
||||
else
|
||||
{:error, :forbidden}
|
||||
end
|
||||
end
|
||||
|
||||
# Legacy objects can be mutated by anybody
|
||||
def authorize_mutation(%Object{}, %User{}), do: true
|
||||
# Legacy objects can be accessed by anybody
|
||||
def authorize_access(%Object{}, %User{}), do: :ok
|
||||
|
||||
@spec get_cached_by_ap_id(String.t()) :: Object.t() | nil
|
||||
def get_cached_by_ap_id(ap_id) do
|
||||
|
@ -183,27 +190,37 @@ def swap_object_with_tombstone(object) do
|
|||
def delete(%Object{data: %{"id" => id}} = object) do
|
||||
with {:ok, _obj} = swap_object_with_tombstone(object),
|
||||
deleted_activity = Activity.delete_all_by_object_ap_id(id),
|
||||
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
|
||||
{:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
|
||||
with true <- Pleroma.Config.get([:instance, :cleanup_attachments]) do
|
||||
{:ok, _} =
|
||||
Pleroma.Workers.AttachmentsCleanupWorker.enqueue("cleanup_attachments", %{
|
||||
"object" => object
|
||||
})
|
||||
end
|
||||
{:ok, _} <- invalid_object_cache(object) do
|
||||
cleanup_attachments(
|
||||
Config.get([:instance, :cleanup_attachments]),
|
||||
%{"object" => object}
|
||||
)
|
||||
|
||||
{:ok, object, deleted_activity}
|
||||
end
|
||||
end
|
||||
|
||||
def prune(%Object{data: %{"id" => id}} = object) do
|
||||
@spec cleanup_attachments(boolean(), %{required(:object) => map()}) ::
|
||||
{:ok, Oban.Job.t() | nil}
|
||||
def cleanup_attachments(true, %{"object" => _} = params) do
|
||||
AttachmentsCleanupWorker.enqueue("cleanup_attachments", params)
|
||||
end
|
||||
|
||||
def cleanup_attachments(_, _), do: {:ok, nil}
|
||||
|
||||
def prune(%Object{data: %{"id" => _id}} = object) do
|
||||
with {:ok, object} <- Repo.delete(object),
|
||||
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
|
||||
{:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
|
||||
{:ok, _} <- invalid_object_cache(object) do
|
||||
{:ok, object}
|
||||
end
|
||||
end
|
||||
|
||||
def invalid_object_cache(%Object{data: %{"id" => id}}) do
|
||||
with {:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do
|
||||
Cachex.del(:web_resp_cache, URI.parse(id).path)
|
||||
end
|
||||
end
|
||||
|
||||
def set_cache(%Object{data: %{"id" => ap_id}} = object) do
|
||||
Cachex.put(:object_cache, "object:#{ap_id}", object)
|
||||
{:ok, object}
|
||||
|
|
|
@ -30,6 +30,25 @@ def checkpw(_password, _password_hash) do
|
|||
false
|
||||
end
|
||||
|
||||
def maybe_update_password(%User{password_hash: "$2" <> _} = user, password) do
|
||||
do_update_password(user, password)
|
||||
end
|
||||
|
||||
def maybe_update_password(%User{password_hash: "$6" <> _} = user, password) do
|
||||
do_update_password(user, password)
|
||||
end
|
||||
|
||||
def maybe_update_password(user, _), do: {:ok, user}
|
||||
|
||||
defp do_update_password(user, password) do
|
||||
user
|
||||
|> User.password_update_changeset(%{
|
||||
"password" => password,
|
||||
"password_confirmation" => password
|
||||
})
|
||||
|> Pleroma.Repo.update()
|
||||
end
|
||||
|
||||
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
|
||||
|
||||
def call(
|
||||
|
@ -42,6 +61,8 @@ def call(
|
|||
_
|
||||
) do
|
||||
if checkpw(password, password_hash) do
|
||||
{:ok, auth_user} = maybe_update_password(auth_user, password)
|
||||
|
||||
conn
|
||||
|> assign(:user, auth_user)
|
||||
|> OAuthScopesPlug.skip_plug()
|
||||
|
|
|
@ -1204,7 +1204,9 @@ def get_users_from_set(ap_ids, local_only \\ true) do
|
|||
def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
|
||||
to = [actor | to]
|
||||
|
||||
User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
|
||||
query = User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
|
||||
|
||||
query
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
|
@ -1430,6 +1432,25 @@ def delete(%User{} = user) do
|
|||
BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
|
||||
end
|
||||
|
||||
defp delete_and_invalidate_cache(%User{} = user) do
|
||||
invalidate_cache(user)
|
||||
Repo.delete(user)
|
||||
end
|
||||
|
||||
defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user)
|
||||
|
||||
defp delete_or_deactivate(%User{local: true} = user) do
|
||||
status = account_status(user)
|
||||
|
||||
if status == :confirmation_pending do
|
||||
delete_and_invalidate_cache(user)
|
||||
else
|
||||
user
|
||||
|> change(%{deactivated: true, email: nil})
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
end
|
||||
|
||||
def perform(:force_password_reset, user), do: force_password_reset(user)
|
||||
|
||||
@spec perform(atom(), User.t()) :: {:ok, User.t()}
|
||||
|
@ -1451,14 +1472,7 @@ def perform(:delete, %User{} = user) do
|
|||
|
||||
delete_user_activities(user)
|
||||
|
||||
if user.local do
|
||||
user
|
||||
|> change(%{deactivated: true, email: nil})
|
||||
|> update_and_set_cache()
|
||||
else
|
||||
invalidate_cache(user)
|
||||
Repo.delete(user)
|
||||
end
|
||||
delete_or_deactivate(user)
|
||||
end
|
||||
|
||||
def perform(:deactivate_async, user, status), do: deactivate(user, status)
|
||||
|
|
|
@ -167,20 +167,18 @@ defp compose_query({:friends, %User{id: id}}, query) do
|
|||
end
|
||||
|
||||
defp compose_query({:recipients_from_activity, to}, query) do
|
||||
query
|
||||
|> join(:left, [u], r in FollowingRelationship,
|
||||
as: :relationships,
|
||||
on: r.follower_id == u.id
|
||||
following_query =
|
||||
from(u in User,
|
||||
join: f in FollowingRelationship,
|
||||
on: u.id == f.following_id,
|
||||
where: f.state == ^:follow_accept,
|
||||
where: u.follower_address in ^to,
|
||||
select: f.follower_id
|
||||
)
|
||||
|
||||
from(u in query,
|
||||
where: u.ap_id in ^to or u.id in subquery(following_query)
|
||||
)
|
||||
|> join(:left, [relationships: r], f in User,
|
||||
as: :following,
|
||||
on: f.id == r.following_id
|
||||
)
|
||||
|> where(
|
||||
[u, following: f, relationships: r],
|
||||
u.ap_id in ^to or (f.follower_address in ^to and r.state == ^:follow_accept)
|
||||
)
|
||||
|> distinct(true)
|
||||
end
|
||||
|
||||
defp compose_query({:order_by, key}, query) do
|
||||
|
|
|
@ -87,6 +87,22 @@ def dictionary(
|
|||
source_to_target_rel_types \\ nil,
|
||||
target_to_source_rel_types \\ nil
|
||||
)
|
||||
|
||||
def dictionary(
|
||||
_source_users,
|
||||
_target_users,
|
||||
[] = _source_to_target_rel_types,
|
||||
[] = _target_to_source_rel_types
|
||||
) do
|
||||
[]
|
||||
end
|
||||
|
||||
def dictionary(
|
||||
source_users,
|
||||
target_users,
|
||||
source_to_target_rel_types,
|
||||
target_to_source_rel_types
|
||||
)
|
||||
when is_list(source_users) and is_list(target_users) do
|
||||
source_user_ids = User.binary_id(source_users)
|
||||
target_user_ids = User.binary_id(target_users)
|
||||
|
@ -138,11 +154,16 @@ def view_relationships_option(nil = _reading_user, _actors, _opts) do
|
|||
|
||||
def view_relationships_option(%User{} = reading_user, actors, opts) do
|
||||
{source_to_target_rel_types, target_to_source_rel_types} =
|
||||
if opts[:source_mutes_only] do
|
||||
# This option is used for rendering statuses (FE needs `muted` flag for each one anyways)
|
||||
{[:mute], []}
|
||||
else
|
||||
{[:block, :mute, :notification_mute, :reblog_mute], [:block, :inverse_subscription]}
|
||||
case opts[:subset] do
|
||||
:source_mutes ->
|
||||
# Used for statuses rendering (FE needs `muted` flag for each status when statuses load)
|
||||
{[:mute], []}
|
||||
|
||||
nil ->
|
||||
{[:block, :mute, :notification_mute, :reblog_mute], [:block, :inverse_subscription]}
|
||||
|
||||
unknown ->
|
||||
raise "Unsupported :subset option value: #{inspect(unknown)}"
|
||||
end
|
||||
|
||||
user_relationships =
|
||||
|
@ -153,7 +174,17 @@ def view_relationships_option(%User{} = reading_user, actors, opts) do
|
|||
target_to_source_rel_types
|
||||
)
|
||||
|
||||
following_relationships = FollowingRelationship.all_between_user_sets([reading_user], actors)
|
||||
following_relationships =
|
||||
case opts[:subset] do
|
||||
:source_mutes ->
|
||||
[]
|
||||
|
||||
nil ->
|
||||
FollowingRelationship.all_between_user_sets([reading_user], actors)
|
||||
|
||||
unknown ->
|
||||
raise "Unsupported :subset option value: #{inspect(unknown)}"
|
||||
end
|
||||
|
||||
%{user_relationships: user_relationships, following_relationships: following_relationships}
|
||||
end
|
||||
|
|
|
@ -22,6 +22,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
alias Pleroma.Web.ActivityPub.Pipeline
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.AdminAPI
|
||||
alias Pleroma.Web.AdminAPI.AccountView
|
||||
alias Pleroma.Web.AdminAPI.ConfigView
|
||||
alias Pleroma.Web.AdminAPI.ModerationLogView
|
||||
|
@ -30,8 +31,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
alias Pleroma.Web.AdminAPI.Search
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.MastodonAPI
|
||||
alias Pleroma.Web.MastodonAPI.AppView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.Router
|
||||
|
||||
|
@ -280,8 +281,8 @@ def list_instance_statuses(conn, %{"instance" => instance} = params) do
|
|||
})
|
||||
|
||||
conn
|
||||
|> put_view(Pleroma.Web.AdminAPI.StatusView)
|
||||
|> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
|
||||
|> put_view(AdminAPI.StatusView)
|
||||
|> render("index.json", %{activities: activities, as: :activity})
|
||||
end
|
||||
|
||||
def list_user_statuses(conn, %{"nickname" => nickname} = params) do
|
||||
|
@ -299,8 +300,8 @@ def list_user_statuses(conn, %{"nickname" => nickname} = params) do
|
|||
})
|
||||
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
|
||||
|> put_view(MastodonAPI.StatusView)
|
||||
|> render("index.json", %{activities: activities, as: :activity})
|
||||
else
|
||||
_ -> {:error, :not_found}
|
||||
end
|
||||
|
@ -829,14 +830,14 @@ def list_statuses(%{assigns: %{user: _admin}} = conn, params) do
|
|||
})
|
||||
|
||||
conn
|
||||
|> put_view(Pleroma.Web.AdminAPI.StatusView)
|
||||
|> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
|
||||
|> put_view(AdminAPI.StatusView)
|
||||
|> render("index.json", %{activities: activities, as: :activity})
|
||||
end
|
||||
|
||||
def status_show(conn, %{"id" => id}) do
|
||||
with %Activity{} = activity <- Activity.get_by_id(id) do
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> put_view(MastodonAPI.StatusView)
|
||||
|> render("show.json", %{activity: activity})
|
||||
else
|
||||
_ -> errors(conn, {:error, :not_found})
|
||||
|
@ -861,7 +862,7 @@ def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
|
|||
})
|
||||
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> put_view(MastodonAPI.StatusView)
|
||||
|> render("show.json", %{activity: activity})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,9 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
|
|||
use Pleroma.Web, :view
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.AdminAPI
|
||||
alias Pleroma.Web.AdminAPI.AccountView
|
||||
alias Pleroma.Web.MastodonAPI
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
def render("index.json", %{users: users, count: count, page_size: page_size}) do
|
||||
|
@ -119,6 +121,13 @@ def render("create-error.json", %{changeset: %Ecto.Changeset{changes: changes, e
|
|||
}
|
||||
end
|
||||
|
||||
def merge_account_views(%User{} = user) do
|
||||
MastodonAPI.AccountView.render("show.json", %{user: user})
|
||||
|> Map.merge(AdminAPI.AccountView.render("show.json", %{user: user}))
|
||||
end
|
||||
|
||||
def merge_account_views(_), do: %{}
|
||||
|
||||
defp parse_error([]), do: ""
|
||||
|
||||
defp parse_error(errors) do
|
||||
|
|
|
@ -7,10 +7,13 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
|
|||
|
||||
alias Pleroma.HTML
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.AdminAPI
|
||||
alias Pleroma.Web.AdminAPI.Report
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
defdelegate merge_account_views(user), to: AdminAPI.AccountView
|
||||
|
||||
def render("index.json", %{reports: reports}) do
|
||||
%{
|
||||
reports:
|
||||
|
@ -41,8 +44,7 @@ def render("show.json", %{report: report, user: user, account: account, statuses
|
|||
statuses:
|
||||
StatusView.render("index.json", %{
|
||||
activities: statuses,
|
||||
as: :activity,
|
||||
skip_relationships: false
|
||||
as: :activity
|
||||
}),
|
||||
state: report.data["state"],
|
||||
notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes})
|
||||
|
@ -70,11 +72,4 @@ def render("show_note.json", %{
|
|||
created_at: Utils.to_masto_date(inserted_at)
|
||||
}
|
||||
end
|
||||
|
||||
defp merge_account_views(%User{} = user) do
|
||||
Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user})
|
||||
|> Map.merge(Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user}))
|
||||
end
|
||||
|
||||
defp merge_account_views(_), do: %{}
|
||||
end
|
||||
|
|
|
@ -7,24 +7,19 @@ defmodule Pleroma.Web.AdminAPI.StatusView do
|
|||
|
||||
require Pleroma.Constants
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.AdminAPI
|
||||
alias Pleroma.Web.MastodonAPI
|
||||
|
||||
defdelegate merge_account_views(user), to: AdminAPI.AccountView
|
||||
|
||||
def render("index.json", opts) do
|
||||
safe_render_many(opts.activities, __MODULE__, "show.json", opts)
|
||||
end
|
||||
|
||||
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
|
||||
user = StatusView.get_user(activity.data["actor"])
|
||||
user = MastodonAPI.StatusView.get_user(activity.data["actor"])
|
||||
|
||||
StatusView.render("show.json", opts)
|
||||
MastodonAPI.StatusView.render("show.json", opts)
|
||||
|> Map.merge(%{account: merge_account_views(user)})
|
||||
end
|
||||
|
||||
defp merge_account_views(%User{} = user) do
|
||||
Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user})
|
||||
|> Map.merge(Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user}))
|
||||
end
|
||||
|
||||
defp merge_account_views(_), do: %{}
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
defmodule Pleroma.Web.ApiSpec.Helpers do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
||||
|
||||
def request_body(description, schema_ref, opts \\ []) do
|
||||
media_types = ["application/json", "multipart/form-data", "application/x-www-form-urlencoded"]
|
||||
|
@ -47,6 +48,15 @@ def pagination_params do
|
|||
]
|
||||
end
|
||||
|
||||
def with_relationships_param do
|
||||
Operation.parameter(
|
||||
:with_relationships,
|
||||
:query,
|
||||
BooleanLike,
|
||||
"Embed relationships into accounts."
|
||||
)
|
||||
end
|
||||
|
||||
def empty_object_response do
|
||||
Operation.response("Empty object", "application/json", %Schema{type: :object, example: %{}})
|
||||
end
|
||||
|
|
|
@ -155,8 +155,10 @@ def followers_operation do
|
|||
security: [%{"oAuth" => ["read:accounts"]}],
|
||||
description:
|
||||
"Accounts which follow the given account, if network is not hidden by the account owner.",
|
||||
parameters:
|
||||
[%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(),
|
||||
parameters: [
|
||||
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
|
||||
with_relationships_param() | pagination_params()
|
||||
],
|
||||
responses: %{
|
||||
200 => Operation.response("Accounts", "application/json", array_of_accounts())
|
||||
}
|
||||
|
@ -171,8 +173,10 @@ def following_operation do
|
|||
security: [%{"oAuth" => ["read:accounts"]}],
|
||||
description:
|
||||
"Accounts which the given account is following, if network is not hidden by the account owner.",
|
||||
parameters:
|
||||
[%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(),
|
||||
parameters: [
|
||||
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
|
||||
with_relationships_param() | pagination_params()
|
||||
],
|
||||
responses: %{200 => Operation.response("Accounts", "application/json", array_of_accounts())}
|
||||
}
|
||||
end
|
||||
|
@ -389,7 +393,7 @@ defp create_request do
|
|||
format: :password
|
||||
},
|
||||
agreement: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
description:
|
||||
"Whether the user agrees to the local rules, terms, and policies. These should be presented to the user in order to allow them to consent before setting this parameter to TRUE."
|
||||
},
|
||||
|
@ -459,7 +463,7 @@ defp update_creadentials_request do
|
|||
type: :object,
|
||||
properties: %{
|
||||
bot: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Whether the account has a bot flag."
|
||||
},
|
||||
|
@ -482,7 +486,7 @@ defp update_creadentials_request do
|
|||
format: :binary
|
||||
},
|
||||
locked: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Whether manual approval of follow requests is required."
|
||||
},
|
||||
|
@ -506,37 +510,37 @@ defp update_creadentials_request do
|
|||
|
||||
# Pleroma-specific fields
|
||||
no_rich_text: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "html tags are stripped from all statuses requested from the API"
|
||||
},
|
||||
hide_followers: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "user's followers will be hidden"
|
||||
},
|
||||
hide_follows: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "user's follows will be hidden"
|
||||
},
|
||||
hide_followers_count: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "user's follower count will be hidden"
|
||||
},
|
||||
hide_follows_count: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "user's follow count will be hidden"
|
||||
},
|
||||
hide_favorites: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "user's favorites timeline will be hidden"
|
||||
},
|
||||
show_role: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "user's role (e.g admin, moderator) will be exposed to anyone in the
|
||||
API"
|
||||
|
@ -548,12 +552,12 @@ defp update_creadentials_request do
|
|||
description: "Opaque user settings to be saved on the backend."
|
||||
},
|
||||
skip_thread_containment: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Skip filtering out broken threads"
|
||||
},
|
||||
allow_following_move: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Allows automatically follow moved following accounts"
|
||||
},
|
||||
|
@ -564,7 +568,7 @@ defp update_creadentials_request do
|
|||
format: :binary
|
||||
},
|
||||
discoverable: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description:
|
||||
"Discovery of this account in search results and other services is allowed."
|
||||
|
@ -674,7 +678,7 @@ defp mute_request do
|
|||
type: :object,
|
||||
properties: %{
|
||||
notifications: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Mute notifications in addition to statuses? Defaults to true.",
|
||||
default: true
|
||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.ApiSpec.FilterOperation do
|
|||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Helpers
|
||||
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
|
@ -171,7 +172,7 @@ defp create_request do
|
|||
type: :object,
|
||||
properties: %{
|
||||
irreversible: %Schema{
|
||||
type: :bolean,
|
||||
allOf: [BooleanLike],
|
||||
description:
|
||||
"Should the server irreversibly drop matching entities from home and notifications?",
|
||||
default: false
|
||||
|
@ -199,13 +200,13 @@ defp update_request do
|
|||
"Array of enumerable strings `home`, `notifications`, `public`, `thread`. At least one context must be specified."
|
||||
},
|
||||
irreversible: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description:
|
||||
"Should the server irreversibly drop matching entities from home and notifications?"
|
||||
},
|
||||
whole_word: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Consider word boundaries?",
|
||||
default: true
|
||||
|
|
|
@ -125,11 +125,17 @@ defp instance do
|
|||
},
|
||||
avatar_upload_limit: %Schema{type: :integer, description: "The title of the website"},
|
||||
background_upload_limit: %Schema{type: :integer, description: "The title of the website"},
|
||||
banner_upload_limit: %Schema{type: :integer, description: "The title of the website"}
|
||||
banner_upload_limit: %Schema{type: :integer, description: "The title of the website"},
|
||||
background_image: %Schema{
|
||||
type: :string,
|
||||
format: :uri,
|
||||
description: "The background image for the website"
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"avatar_upload_limit" => 2_000_000,
|
||||
"background_upload_limit" => 4_000_000,
|
||||
"background_image" => "/static/image.png",
|
||||
"banner_upload_limit" => 4_000_000,
|
||||
"description" => "A Pleroma instance, an alternative fediverse server",
|
||||
"email" => "lain@lain.com",
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.MediaOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Helpers
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Attachment
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def create_operation do
|
||||
%Operation{
|
||||
tags: ["media"],
|
||||
summary: "Upload media as attachment",
|
||||
description: "Creates an attachment to be used with a new status.",
|
||||
operationId: "MediaController.create",
|
||||
security: [%{"oAuth" => ["write:media"]}],
|
||||
requestBody: Helpers.request_body("Parameters", create_request()),
|
||||
responses: %{
|
||||
200 => Operation.response("Media", "application/json", Attachment),
|
||||
401 => Operation.response("Media", "application/json", ApiError),
|
||||
422 => Operation.response("Media", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp create_request do
|
||||
%Schema{
|
||||
title: "MediaCreateRequest",
|
||||
description: "POST body for creating an attachment",
|
||||
type: :object,
|
||||
required: [:file],
|
||||
properties: %{
|
||||
file: %Schema{
|
||||
type: :string,
|
||||
format: :binary,
|
||||
description: "The file to be attached, using multipart form data."
|
||||
},
|
||||
description: %Schema{
|
||||
type: :string,
|
||||
description: "A plain-text description of the media, for accessibility purposes."
|
||||
},
|
||||
focus: %Schema{
|
||||
type: :string,
|
||||
description: "Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0."
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_operation do
|
||||
%Operation{
|
||||
tags: ["media"],
|
||||
summary: "Upload media as attachment",
|
||||
description: "Creates an attachment to be used with a new status.",
|
||||
operationId: "MediaController.update",
|
||||
security: [%{"oAuth" => ["write:media"]}],
|
||||
parameters: [id_param()],
|
||||
requestBody: Helpers.request_body("Parameters", update_request()),
|
||||
responses: %{
|
||||
200 => Operation.response("Media", "application/json", Attachment),
|
||||
400 => Operation.response("Media", "application/json", ApiError),
|
||||
401 => Operation.response("Media", "application/json", ApiError),
|
||||
422 => Operation.response("Media", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp update_request do
|
||||
%Schema{
|
||||
title: "MediaUpdateRequest",
|
||||
description: "POST body for updating an attachment",
|
||||
type: :object,
|
||||
properties: %{
|
||||
file: %Schema{
|
||||
type: :string,
|
||||
format: :binary,
|
||||
description: "The file to be attached, using multipart form data."
|
||||
},
|
||||
description: %Schema{
|
||||
type: :string,
|
||||
description: "A plain-text description of the media, for accessibility purposes."
|
||||
},
|
||||
focus: %Schema{
|
||||
type: :string,
|
||||
description: "Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0."
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def show_operation do
|
||||
%Operation{
|
||||
tags: ["media"],
|
||||
summary: "Show Uploaded media attachment",
|
||||
operationId: "MediaController.show",
|
||||
parameters: [id_param()],
|
||||
security: [%{"oAuth" => ["read:media"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("Media", "application/json", Attachment),
|
||||
401 => Operation.response("Media", "application/json", ApiError),
|
||||
422 => Operation.response("Media", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def create2_operation do
|
||||
%Operation{
|
||||
tags: ["media"],
|
||||
summary: "Upload media as attachment",
|
||||
description: "Creates an attachment to be used with a new status.",
|
||||
operationId: "MediaController.create2",
|
||||
security: [%{"oAuth" => ["write:media"]}],
|
||||
requestBody: Helpers.request_body("Parameters", create_request()),
|
||||
responses: %{
|
||||
202 => Operation.response("Media", "application/json", Attachment),
|
||||
422 => Operation.response("Media", "application/json", ApiError),
|
||||
500 => Operation.response("Media", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp id_param do
|
||||
Operation.parameter(:id, :path, :string, "The ID of the Attachment entity")
|
||||
end
|
||||
end
|
|
@ -0,0 +1,390 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
|
||||
import Pleroma.Web.ApiSpec.Helpers
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def remote_operation do
|
||||
%Operation{
|
||||
tags: ["Emoji Packs"],
|
||||
summary: "Make request to another instance for emoji packs list",
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
parameters: [url_param()],
|
||||
operationId: "PleromaAPI.EmojiPackController.remote",
|
||||
responses: %{
|
||||
200 => emoji_packs_response(),
|
||||
500 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["Emoji Packs"],
|
||||
summary: "Lists local custom emoji packs",
|
||||
operationId: "PleromaAPI.EmojiPackController.index",
|
||||
responses: %{
|
||||
200 => emoji_packs_response()
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def show_operation do
|
||||
%Operation{
|
||||
tags: ["Emoji Packs"],
|
||||
summary: "Show emoji pack",
|
||||
operationId: "PleromaAPI.EmojiPackController.show",
|
||||
parameters: [name_param()],
|
||||
responses: %{
|
||||
200 => Operation.response("Emoji Pack", "application/json", emoji_pack()),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def archive_operation do
|
||||
%Operation{
|
||||
tags: ["Emoji Packs"],
|
||||
summary: "Requests a local pack archive from the instance",
|
||||
operationId: "PleromaAPI.EmojiPackController.archive",
|
||||
parameters: [name_param()],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Archive file", "application/octet-stream", %Schema{
|
||||
type: :string,
|
||||
format: :binary
|
||||
}),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def download_operation do
|
||||
%Operation{
|
||||
tags: ["Emoji Packs"],
|
||||
summary: "Download pack from another instance",
|
||||
operationId: "PleromaAPI.EmojiPackController.download",
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
requestBody: request_body("Parameters", download_request(), required: true),
|
||||
responses: %{
|
||||
200 => ok_response(),
|
||||
500 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp download_request do
|
||||
%Schema{
|
||||
type: :object,
|
||||
required: [:url, :name],
|
||||
properties: %{
|
||||
url: %Schema{
|
||||
type: :string,
|
||||
format: :uri,
|
||||
description: "URL of the instance to download from"
|
||||
},
|
||||
name: %Schema{type: :string, format: :uri, description: "Pack Name"},
|
||||
as: %Schema{type: :string, format: :uri, description: "Save as"}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def create_operation do
|
||||
%Operation{
|
||||
tags: ["Emoji Packs"],
|
||||
summary: "Create an empty pack",
|
||||
operationId: "PleromaAPI.EmojiPackController.create",
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
parameters: [name_param()],
|
||||
responses: %{
|
||||
200 => ok_response(),
|
||||
400 => Operation.response("Not Found", "application/json", ApiError),
|
||||
409 => Operation.response("Conflict", "application/json", ApiError),
|
||||
500 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def delete_operation do
|
||||
%Operation{
|
||||
tags: ["Emoji Packs"],
|
||||
summary: "Delete a custom emoji pack",
|
||||
operationId: "PleromaAPI.EmojiPackController.delete",
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
parameters: [name_param()],
|
||||
responses: %{
|
||||
200 => ok_response(),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_operation do
|
||||
%Operation{
|
||||
tags: ["Emoji Packs"],
|
||||
summary: "Updates (replaces) pack metadata",
|
||||
operationId: "PleromaAPI.EmojiPackController.update",
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
requestBody: request_body("Parameters", update_request(), required: true),
|
||||
parameters: [name_param()],
|
||||
responses: %{
|
||||
200 => Operation.response("Metadata", "application/json", metadata()),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def add_file_operation do
|
||||
%Operation{
|
||||
tags: ["Emoji Packs"],
|
||||
summary: "Add new file to the pack",
|
||||
operationId: "PleromaAPI.EmojiPackController.add_file",
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
requestBody: request_body("Parameters", add_file_request(), required: true),
|
||||
parameters: [name_param()],
|
||||
responses: %{
|
||||
200 => Operation.response("Files Object", "application/json", files_object()),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||
409 => Operation.response("Conflict", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp add_file_request do
|
||||
%Schema{
|
||||
type: :object,
|
||||
required: [:file],
|
||||
properties: %{
|
||||
file: %Schema{
|
||||
description:
|
||||
"File needs to be uploaded with the multipart request or link to remote file",
|
||||
anyOf: [
|
||||
%Schema{type: :string, format: :binary},
|
||||
%Schema{type: :string, format: :uri}
|
||||
]
|
||||
},
|
||||
shortcode: %Schema{
|
||||
type: :string,
|
||||
description:
|
||||
"Shortcode for new emoji, must be unique for all emoji. If not sended, shortcode will be taken from original filename."
|
||||
},
|
||||
filename: %Schema{
|
||||
type: :string,
|
||||
description:
|
||||
"New emoji file name. If not specified will be taken from original filename."
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_file_operation do
|
||||
%Operation{
|
||||
tags: ["Emoji Packs"],
|
||||
summary: "Add new file to the pack",
|
||||
operationId: "PleromaAPI.EmojiPackController.update_file",
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
requestBody: request_body("Parameters", update_file_request(), required: true),
|
||||
parameters: [name_param()],
|
||||
responses: %{
|
||||
200 => Operation.response("Files Object", "application/json", files_object()),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||
409 => Operation.response("Conflict", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp update_file_request do
|
||||
%Schema{
|
||||
type: :object,
|
||||
required: [:shortcode, :new_shortcode, :new_filename],
|
||||
properties: %{
|
||||
shortcode: %Schema{
|
||||
type: :string,
|
||||
description: "Emoji file shortcode"
|
||||
},
|
||||
new_shortcode: %Schema{
|
||||
type: :string,
|
||||
description: "New emoji file shortcode"
|
||||
},
|
||||
new_filename: %Schema{
|
||||
type: :string,
|
||||
description: "New filename for emoji file"
|
||||
},
|
||||
force: %Schema{
|
||||
type: :boolean,
|
||||
description: "With true value to overwrite existing emoji with new shortcode",
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def delete_file_operation do
|
||||
%Operation{
|
||||
tags: ["Emoji Packs"],
|
||||
summary: "Delete emoji file from pack",
|
||||
operationId: "PleromaAPI.EmojiPackController.delete_file",
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
parameters: [
|
||||
name_param(),
|
||||
Operation.parameter(:shortcode, :query, :string, "File shortcode",
|
||||
example: "cofe",
|
||||
required: true
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 => Operation.response("Files Object", "application/json", files_object()),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def import_from_filesystem_operation do
|
||||
%Operation{
|
||||
tags: ["Emoji Packs"],
|
||||
summary: "Imports packs from filesystem",
|
||||
operationId: "PleromaAPI.EmojiPackController.import",
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Array of imported pack names", "application/json", %Schema{
|
||||
type: :array,
|
||||
items: %Schema{type: :string}
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp name_param do
|
||||
Operation.parameter(:name, :path, :string, "Pack Name", example: "cofe", required: true)
|
||||
end
|
||||
|
||||
defp url_param do
|
||||
Operation.parameter(
|
||||
:url,
|
||||
:query,
|
||||
%Schema{type: :string, format: :uri},
|
||||
"URL of the instance",
|
||||
required: true
|
||||
)
|
||||
end
|
||||
|
||||
defp ok_response do
|
||||
Operation.response("Ok", "application/json", %Schema{type: :string, example: "ok"})
|
||||
end
|
||||
|
||||
defp emoji_packs_response do
|
||||
Operation.response(
|
||||
"Object with pack names as keys and pack contents as values",
|
||||
"application/json",
|
||||
%Schema{
|
||||
type: :object,
|
||||
additionalProperties: emoji_pack(),
|
||||
example: %{
|
||||
"emojos" => emoji_pack().example
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
defp emoji_pack do
|
||||
%Schema{
|
||||
title: "EmojiPack",
|
||||
type: :object,
|
||||
properties: %{
|
||||
files: files_object(),
|
||||
pack: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
license: %Schema{type: :string},
|
||||
homepage: %Schema{type: :string, format: :uri},
|
||||
description: %Schema{type: :string},
|
||||
"can-download": %Schema{type: :boolean},
|
||||
"share-files": %Schema{type: :boolean},
|
||||
"download-sha256": %Schema{type: :string}
|
||||
}
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"files" => %{"emacs" => "emacs.png", "guix" => "guix.png"},
|
||||
"pack" => %{
|
||||
"license" => "Test license",
|
||||
"homepage" => "https://pleroma.social",
|
||||
"description" => "Test description",
|
||||
"can-download" => true,
|
||||
"share-files" => true,
|
||||
"download-sha256" => "57482F30674FD3DE821FF48C81C00DA4D4AF1F300209253684ABA7075E5FC238"
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp files_object do
|
||||
%Schema{
|
||||
type: :object,
|
||||
additionalProperties: %Schema{type: :string},
|
||||
description: "Object with emoji names as keys and filenames as values"
|
||||
}
|
||||
end
|
||||
|
||||
defp update_request do
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
metadata: %Schema{
|
||||
type: :object,
|
||||
description: "Metadata to replace the old one",
|
||||
properties: %{
|
||||
license: %Schema{type: :string},
|
||||
homepage: %Schema{type: :string, format: :uri},
|
||||
description: %Schema{type: :string},
|
||||
"fallback-src": %Schema{
|
||||
type: :string,
|
||||
format: :uri,
|
||||
description: "Fallback url to download pack from"
|
||||
},
|
||||
"fallback-src-sha256": %Schema{
|
||||
type: :string,
|
||||
description: "SHA256 encoded for fallback pack archive"
|
||||
},
|
||||
"share-files": %Schema{type: :boolean, description: "Is pack allowed for sharing?"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp metadata do
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
license: %Schema{type: :string},
|
||||
homepage: %Schema{type: :string, format: :uri},
|
||||
description: %Schema{type: :string},
|
||||
"fallback-src": %Schema{
|
||||
type: :string,
|
||||
format: :uri,
|
||||
description: "Fallback url to download pack from"
|
||||
},
|
||||
"fallback-src-sha256": %Schema{
|
||||
type: :string,
|
||||
description: "SHA256 encoded for fallback pack archive"
|
||||
},
|
||||
"share-files": %Schema{type: :boolean, description: "Is pack allowed for sharing?"}
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,79 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.PleromaMascotOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
|
||||
import Pleroma.Web.ApiSpec.Helpers
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def show_operation do
|
||||
%Operation{
|
||||
tags: ["Mascot"],
|
||||
summary: "Gets user mascot image",
|
||||
security: [%{"oAuth" => ["read:accounts"]}],
|
||||
operationId: "PleromaAPI.MascotController.show",
|
||||
responses: %{
|
||||
200 => Operation.response("Mascot", "application/json", mascot())
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_operation do
|
||||
%Operation{
|
||||
tags: ["Mascot"],
|
||||
summary: "Set/clear user avatar image",
|
||||
description:
|
||||
"Behaves exactly the same as `POST /api/v1/upload`. Can only accept images - any attempt to upload non-image files will be met with `HTTP 415 Unsupported Media Type`.",
|
||||
operationId: "PleromaAPI.MascotController.update",
|
||||
requestBody:
|
||||
request_body(
|
||||
"Parameters",
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
file: %Schema{type: :string, format: :binary}
|
||||
}
|
||||
},
|
||||
required: true
|
||||
),
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("Mascot", "application/json", mascot()),
|
||||
415 => Operation.response("Unsupported Media Type", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp mascot do
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :string},
|
||||
url: %Schema{type: :string, format: :uri},
|
||||
type: %Schema{type: :string},
|
||||
pleroma: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
mime_type: %Schema{type: :string}
|
||||
}
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"id" => "abcdefg",
|
||||
"url" => "https://pleroma.example.org/media/abcdefg.png",
|
||||
"type" => "image",
|
||||
"pleroma" => %{
|
||||
"mime_type" => "image/png"
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,102 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Reference
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Account
|
||||
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
|
||||
|
||||
import Pleroma.Web.ApiSpec.Helpers
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def create_operation do
|
||||
%Operation{
|
||||
tags: ["Scrobbles"],
|
||||
summary: "Creates a new Listen activity for an account",
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
operationId: "PleromaAPI.ScrobbleController.create",
|
||||
requestBody: request_body("Parameters", create_request(), requried: true),
|
||||
responses: %{
|
||||
200 => Operation.response("Scrobble", "application/json", scrobble())
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["Scrobbles"],
|
||||
summary: "Requests a list of current and recent Listen activities for an account",
|
||||
operationId: "PleromaAPI.ScrobbleController.index",
|
||||
parameters: [
|
||||
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"} | pagination_params()
|
||||
],
|
||||
security: [%{"oAuth" => ["read"]}],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Array of Scrobble", "application/json", %Schema{
|
||||
type: :array,
|
||||
items: scrobble()
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp create_request do
|
||||
%Schema{
|
||||
type: :object,
|
||||
required: [:title],
|
||||
properties: %{
|
||||
title: %Schema{type: :string, description: "The title of the media playing"},
|
||||
album: %Schema{type: :string, description: "The album of the media playing"},
|
||||
artist: %Schema{type: :string, description: "The artist of the media playing"},
|
||||
length: %Schema{type: :integer, description: "The length of the media playing"},
|
||||
visibility: %Schema{
|
||||
allOf: [VisibilityScope],
|
||||
default: "public",
|
||||
description: "Scrobble visibility"
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"title" => "Some Title",
|
||||
"artist" => "Some Artist",
|
||||
"album" => "Some Album",
|
||||
"length" => 180_000
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp scrobble do
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :string},
|
||||
account: Account,
|
||||
title: %Schema{type: :string, description: "The title of the media playing"},
|
||||
album: %Schema{type: :string, description: "The album of the media playing"},
|
||||
artist: %Schema{type: :string, description: "The artist of the media playing"},
|
||||
length: %Schema{
|
||||
type: :integer,
|
||||
description: "The length of the media playing",
|
||||
nullable: true
|
||||
},
|
||||
created_at: %Schema{type: :string, format: :"date-time"}
|
||||
},
|
||||
example: %{
|
||||
"id" => "1234",
|
||||
"account" => Account.schema().example,
|
||||
"title" => "Some Title",
|
||||
"artist" => "Some Artist",
|
||||
"album" => "Some Album",
|
||||
"length" => 180_000,
|
||||
"created_at" => "2019-09-28T12:40:45.000Z"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do
|
|||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Helpers
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
|
@ -47,7 +48,7 @@ defp create_request do
|
|||
description: "Reason for the report"
|
||||
},
|
||||
forward: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
default: false,
|
||||
description:
|
||||
|
|
|
@ -19,6 +19,7 @@ def open_api_operation(action) do
|
|||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
# Note: `with_relationships` param is not supported (PleromaFE uses this op for autocomplete)
|
||||
def account_search_operation do
|
||||
%Operation{
|
||||
tags: ["Search"],
|
||||
|
@ -96,8 +97,8 @@ def search_operation do
|
|||
:query,
|
||||
%Schema{type: :integer},
|
||||
"Offset"
|
||||
)
|
||||
| pagination_params()
|
||||
),
|
||||
with_relationships_param() | pagination_params()
|
||||
],
|
||||
responses: %{
|
||||
200 => Operation.response("Results", "application/json", results())
|
||||
|
@ -138,8 +139,8 @@ def search2_operation do
|
|||
:query,
|
||||
%Schema{allOf: [BooleanLike], default: false},
|
||||
"Only include accounts that the user is following"
|
||||
)
|
||||
| pagination_params()
|
||||
),
|
||||
with_relationships_param() | pagination_params()
|
||||
],
|
||||
responses: %{
|
||||
200 => Operation.response("Results", "application/json", results2())
|
||||
|
|
|
@ -349,10 +349,7 @@ def bookmarks_operation do
|
|||
summary: "Bookmarked statuses",
|
||||
description: "Statuses the user has bookmarked",
|
||||
operationId: "StatusController.bookmarks",
|
||||
parameters: [
|
||||
Operation.parameter(:with_relationships, :query, BooleanLike, "Include relationships")
|
||||
| pagination_params()
|
||||
],
|
||||
parameters: pagination_params(),
|
||||
security: [%{"oAuth" => ["read:bookmarks"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
|
||||
|
@ -398,12 +395,12 @@ defp create_request do
|
|||
"Duration the poll should be open, in seconds. Must be provided with `poll[options]`"
|
||||
},
|
||||
multiple: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Allow multiple choices?"
|
||||
},
|
||||
hide_totals: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Hide vote counts until the poll ends?"
|
||||
}
|
||||
|
@ -415,7 +412,7 @@ defp create_request do
|
|||
description: "ID of the status being replied to, if status is a reply"
|
||||
},
|
||||
sensitive: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Mark status and attached media as sensitive?"
|
||||
},
|
||||
|
@ -439,7 +436,7 @@ defp create_request do
|
|||
},
|
||||
# Pleroma-specific properties:
|
||||
preview: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description:
|
||||
"If set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example"
|
||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation do
|
|||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Helpers
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
||||
alias Pleroma.Web.ApiSpec.Schemas.PushSubscription
|
||||
|
||||
def open_api_operation(action) do
|
||||
|
@ -117,27 +118,27 @@ defp create_request do
|
|||
type: :object,
|
||||
properties: %{
|
||||
follow: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Receive follow notifications?"
|
||||
},
|
||||
favourite: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Receive favourite notifications?"
|
||||
},
|
||||
reblog: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Receive reblog notifications?"
|
||||
},
|
||||
mention: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Receive mention notifications?"
|
||||
},
|
||||
poll: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Receive poll notifications?"
|
||||
}
|
||||
|
@ -181,27 +182,27 @@ defp update_request do
|
|||
type: :object,
|
||||
properties: %{
|
||||
follow: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Receive follow notifications?"
|
||||
},
|
||||
favourite: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Receive favourite notifications?"
|
||||
},
|
||||
reblog: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Receive reblog notifications?"
|
||||
},
|
||||
mention: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Receive mention notifications?"
|
||||
},
|
||||
poll: %Schema{
|
||||
type: :boolean,
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Receive poll notifications?"
|
||||
}
|
||||
|
|
|
@ -27,8 +27,7 @@ def home_operation do
|
|||
local_param(),
|
||||
with_muted_param(),
|
||||
exclude_visibilities_param(),
|
||||
reply_visibility_param(),
|
||||
with_relationships_param() | pagination_params()
|
||||
reply_visibility_param() | pagination_params()
|
||||
],
|
||||
operationId: "TimelineController.home",
|
||||
responses: %{
|
||||
|
@ -44,7 +43,7 @@ def direct_operation do
|
|||
description:
|
||||
"View statuses with a “direct” privacy, from your account or in your notifications",
|
||||
deprecated: true,
|
||||
parameters: pagination_params(),
|
||||
parameters: [with_muted_param() | pagination_params()],
|
||||
security: [%{"oAuth" => ["read:statuses"]}],
|
||||
operationId: "TimelineController.direct",
|
||||
responses: %{
|
||||
|
@ -63,8 +62,7 @@ def public_operation do
|
|||
only_media_param(),
|
||||
with_muted_param(),
|
||||
exclude_visibilities_param(),
|
||||
reply_visibility_param(),
|
||||
with_relationships_param() | pagination_params()
|
||||
reply_visibility_param() | pagination_params()
|
||||
],
|
||||
operationId: "TimelineController.public",
|
||||
responses: %{
|
||||
|
@ -109,8 +107,7 @@ def hashtag_operation do
|
|||
local_param(),
|
||||
only_media_param(),
|
||||
with_muted_param(),
|
||||
exclude_visibilities_param(),
|
||||
with_relationships_param() | pagination_params()
|
||||
exclude_visibilities_param() | pagination_params()
|
||||
],
|
||||
operationId: "TimelineController.hashtag",
|
||||
responses: %{
|
||||
|
@ -134,8 +131,7 @@ def list_operation do
|
|||
required: true
|
||||
),
|
||||
with_muted_param(),
|
||||
exclude_visibilities_param(),
|
||||
with_relationships_param() | pagination_params()
|
||||
exclude_visibilities_param() | pagination_params()
|
||||
],
|
||||
operationId: "TimelineController.list",
|
||||
responses: %{
|
||||
|
@ -153,10 +149,6 @@ defp array_of_statuses do
|
|||
}
|
||||
end
|
||||
|
||||
defp with_relationships_param do
|
||||
Operation.parameter(:with_relationships, :query, BooleanLike, "Include relationships")
|
||||
end
|
||||
|
||||
defp local_param do
|
||||
Operation.parameter(
|
||||
:local,
|
||||
|
|
|
@ -13,7 +13,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Attachment do
|
|||
type: :object,
|
||||
requried: [:id, :url, :preview_url],
|
||||
properties: %{
|
||||
id: %Schema{type: :string},
|
||||
id: %Schema{type: :string, description: "The ID of the attachment in the database."},
|
||||
url: %Schema{
|
||||
type: :string,
|
||||
format: :uri,
|
||||
|
|
|
@ -16,7 +16,8 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
|
|||
def get_user(%Plug.Conn{} = conn) do
|
||||
with {:ok, {name, password}} <- fetch_credentials(conn),
|
||||
{_, %User{} = user} <- {:user, fetch_user(name)},
|
||||
{_, true} <- {:checkpw, AuthenticationPlug.checkpw(password, user.password_hash)} do
|
||||
{_, true} <- {:checkpw, AuthenticationPlug.checkpw(password, user.password_hash)},
|
||||
{:ok, user} <- AuthenticationPlug.maybe_update_password(user, password) do
|
||||
{:ok, user}
|
||||
else
|
||||
{:error, _reason} = error -> error
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Auth.TOTPAuthenticator do
|
||||
|
|
|
@ -23,6 +23,7 @@ def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}}
|
|||
if String.length(text) in 1..Pleroma.Config.get([:instance, :chat_limit]) do
|
||||
author = User.get_cached_by_nickname(user_name)
|
||||
author = Pleroma.Web.MastodonAPI.AccountView.render("show.json", user: author)
|
||||
|
||||
message = ChatChannelState.add_message(%{text: text, author: author})
|
||||
|
||||
broadcast!(socket, "new_msg", message)
|
||||
|
|
|
@ -347,11 +347,14 @@ def check_expiry_date(expiry_str) do
|
|||
|> check_expiry_date()
|
||||
end
|
||||
|
||||
def listen(user, %{"title" => _} = data) do
|
||||
with visibility <- data["visibility"] || "public",
|
||||
{to, cc} <- get_to_and_cc(user, [], nil, visibility, nil),
|
||||
def listen(user, data) do
|
||||
visibility = Map.get(data, :visibility, "public")
|
||||
|
||||
with {to, cc} <- get_to_and_cc(user, [], nil, visibility, nil),
|
||||
listen_data <-
|
||||
Map.take(data, ["album", "artist", "title", "length"])
|
||||
data
|
||||
|> Map.take([:album, :artist, :title, :length])
|
||||
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|
||||
|> Map.put("type", "Audio")
|
||||
|> Map.put("to", to)
|
||||
|> Map.put("cc", cc)
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
defmodule Pleroma.Web.ControllerHelper do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Config
|
||||
|
||||
# As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
|
||||
@falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"]
|
||||
|
||||
|
@ -106,13 +104,16 @@ def put_if_exist(map, _key, nil), do: map
|
|||
|
||||
def put_if_exist(map, key, value), do: Map.put(map, key, value)
|
||||
|
||||
@doc "Whether to skip rendering `[:account][:pleroma][:relationship]`for statuses/notifications"
|
||||
def skip_relationships?(params) do
|
||||
if Config.get([:extensions, :output_relationships_in_statuses_by_default]) do
|
||||
false
|
||||
else
|
||||
# BREAKING: older PleromaFE versions do not send this param but _do_ expect relationships.
|
||||
not truthy_param?(params["with_relationships"])
|
||||
end
|
||||
@doc """
|
||||
Returns true if request specifies to include embedded relationships in account objects.
|
||||
May only be used in selected account-related endpoints; has no effect for status- or
|
||||
notification-related endpoints.
|
||||
"""
|
||||
# Intended for PleromaFE: https://git.pleroma.social/pleroma/pleroma-fe/-/issues/838
|
||||
def embed_relationships?(params) do
|
||||
# To do once OpenAPI transition mess is over: just `truthy_param?(params[:with_relationships])`
|
||||
params
|
||||
|> Map.get(:with_relationships, params["with_relationships"])
|
||||
|> truthy_param?()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,8 +10,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
add_link_headers: 2,
|
||||
truthy_param?: 1,
|
||||
assign_account_by_id: 2,
|
||||
json_response: 3,
|
||||
skip_relationships?: 1
|
||||
embed_relationships?: 1,
|
||||
json_response: 3
|
||||
]
|
||||
|
||||
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
|
||||
|
@ -177,6 +177,7 @@ def update_credentials(%{assigns: %{user: original_user}, body_params: params} =
|
|||
)
|
||||
|> add_if_present(params, :pleroma_settings_store, :pleroma_settings_store)
|
||||
|> add_if_present(params, :default_scope, :default_scope)
|
||||
|> add_if_present(params["source"], "privacy", :default_scope)
|
||||
|> add_if_present(params, :actor_type, :actor_type)
|
||||
|
||||
changeset = User.update_changeset(user, user_params)
|
||||
|
@ -189,7 +190,8 @@ def update_credentials(%{assigns: %{user: original_user}, body_params: params} =
|
|||
end
|
||||
|
||||
defp add_if_present(map, params, params_field, map_field, value_function \\ &{:ok, &1}) do
|
||||
with true <- Map.has_key?(params, params_field),
|
||||
with true <- is_map(params),
|
||||
true <- Map.has_key?(params, params_field),
|
||||
{:ok, new_value} <- value_function.(Map.get(params, params_field)) do
|
||||
Map.put(map, map_field, new_value)
|
||||
else
|
||||
|
@ -247,8 +249,7 @@ def statuses(%{assigns: %{user: reading_user}} = conn, params) do
|
|||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: reading_user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
as: :activity
|
||||
)
|
||||
else
|
||||
_e -> render_error(conn, :not_found, "Can't find user")
|
||||
|
@ -271,7 +272,13 @@ def followers(%{assigns: %{user: for_user, account: user}} = conn, params) do
|
|||
|
||||
conn
|
||||
|> add_link_headers(followers)
|
||||
|> render("index.json", for: for_user, users: followers, as: :user)
|
||||
# https://git.pleroma.social/pleroma/pleroma-fe/-/issues/838#note_59223
|
||||
|> render("index.json",
|
||||
for: for_user,
|
||||
users: followers,
|
||||
as: :user,
|
||||
embed_relationships: embed_relationships?(params)
|
||||
)
|
||||
end
|
||||
|
||||
@doc "GET /api/v1/accounts/:id/following"
|
||||
|
@ -290,7 +297,13 @@ def following(%{assigns: %{user: for_user, account: user}} = conn, params) do
|
|||
|
||||
conn
|
||||
|> add_link_headers(followers)
|
||||
|> render("index.json", for: for_user, users: followers, as: :user)
|
||||
# https://git.pleroma.social/pleroma/pleroma-fe/-/issues/838#note_59223
|
||||
|> render("index.json",
|
||||
for: for_user,
|
||||
users: followers,
|
||||
as: :user,
|
||||
embed_relationships: embed_relationships?(params)
|
||||
)
|
||||
end
|
||||
|
||||
@doc "GET /api/v1/accounts/:id/lists"
|
||||
|
|
|
@ -20,6 +20,10 @@ def call(conn, {:error, :not_found}) do
|
|||
render_error(conn, :not_found, "Record not found")
|
||||
end
|
||||
|
||||
def call(conn, {:error, :forbidden}) do
|
||||
render_error(conn, :forbidden, "Access denied")
|
||||
end
|
||||
|
||||
def call(conn, {:error, error_message}) do
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|
|
|
@ -11,17 +11,21 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
|
|||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
|
||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["write:media"]})
|
||||
plug(OAuthScopesPlug, %{scopes: ["read:media"]} when action == :show)
|
||||
plug(OAuthScopesPlug, %{scopes: ["write:media"]} when action != :show)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.MediaOperation
|
||||
|
||||
@doc "POST /api/v1/media"
|
||||
def create(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
|
||||
def create(%{assigns: %{user: user}, body_params: %{file: file} = data} = conn, _) do
|
||||
with {:ok, object} <-
|
||||
ActivityPub.upload(
|
||||
file,
|
||||
actor: User.ap_id(user),
|
||||
description: Map.get(data, "description")
|
||||
description: Map.get(data, :description)
|
||||
) do
|
||||
attachment_data = Map.put(object.data, "id", object.id)
|
||||
|
||||
|
@ -29,11 +33,30 @@ def create(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
|
|||
end
|
||||
end
|
||||
|
||||
def create(_conn, _data), do: {:error, :bad_request}
|
||||
|
||||
@doc "POST /api/v2/media"
|
||||
def create2(%{assigns: %{user: user}, body_params: %{file: file} = data} = conn, _) do
|
||||
with {:ok, object} <-
|
||||
ActivityPub.upload(
|
||||
file,
|
||||
actor: User.ap_id(user),
|
||||
description: Map.get(data, :description)
|
||||
) do
|
||||
attachment_data = Map.put(object.data, "id", object.id)
|
||||
|
||||
conn
|
||||
|> put_status(202)
|
||||
|> render("attachment.json", %{attachment: attachment_data})
|
||||
end
|
||||
end
|
||||
|
||||
def create2(_conn, _data), do: {:error, :bad_request}
|
||||
|
||||
@doc "PUT /api/v1/media/:id"
|
||||
def update(%{assigns: %{user: user}} = conn, %{"id" => id, "description" => description})
|
||||
when is_binary(description) do
|
||||
def update(%{assigns: %{user: user}, body_params: %{description: description}} = conn, %{id: id}) do
|
||||
with %Object{} = object <- Object.get_by_id(id),
|
||||
true <- Object.authorize_mutation(object, user),
|
||||
:ok <- Object.authorize_access(object, user),
|
||||
{:ok, %Object{data: data}} <- Object.update_data(object, %{"name" => description}) do
|
||||
attachment_data = Map.put(data, "id", object.id)
|
||||
|
||||
|
@ -41,5 +64,17 @@ def update(%{assigns: %{user: user}} = conn, %{"id" => id, "description" => desc
|
|||
end
|
||||
end
|
||||
|
||||
def update(_conn, _data), do: {:error, :bad_request}
|
||||
def update(conn, data), do: show(conn, data)
|
||||
|
||||
@doc "GET /api/v1/media/:id"
|
||||
def show(%{assigns: %{user: user}} = conn, %{id: id}) do
|
||||
with %Object{data: data, id: object_id} = object <- Object.get_by_id(id),
|
||||
:ok <- Object.authorize_access(object, user) do
|
||||
attachment_data = Map.put(data, "id", object_id)
|
||||
|
||||
render(conn, "attachment.json", %{attachment: attachment_data})
|
||||
end
|
||||
end
|
||||
|
||||
def show(_conn, _data), do: {:error, :bad_request}
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.NotificationController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
|
||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
||||
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
|
@ -50,8 +50,7 @@ def index(%{assigns: %{user: user}} = conn, params) do
|
|||
|> add_link_headers(notifications)
|
||||
|> render("index.json",
|
||||
notifications: notifications,
|
||||
for: user,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
for: user
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -5,14 +5,13 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.SearchController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [skip_relationships?: 1]
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.Plugs.RateLimiter
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ControllerHelper
|
||||
alias Pleroma.Web.MastodonAPI.AccountView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
|
@ -34,7 +33,11 @@ def account_search(%{assigns: %{user: user}} = conn, %{q: query} = params) do
|
|||
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("index.json", users: accounts, for: user, as: :user)
|
||||
|> render("index.json",
|
||||
users: accounts,
|
||||
for: user,
|
||||
as: :user
|
||||
)
|
||||
end
|
||||
|
||||
def search2(conn, params), do: do_search(:v2, conn, params)
|
||||
|
@ -71,13 +74,13 @@ defp do_search(version, %{assigns: %{user: user}} = conn, %{q: query} = params)
|
|||
|
||||
defp search_options(params, user) do
|
||||
[
|
||||
skip_relationships: skip_relationships?(params),
|
||||
resolve: params[:resolve],
|
||||
following: params[:following],
|
||||
limit: params[:limit],
|
||||
offset: params[:offset],
|
||||
type: params[:type],
|
||||
author: get_author(params),
|
||||
embed_relationships: ControllerHelper.embed_relationships?(params),
|
||||
for_user: user
|
||||
]
|
||||
|> Enum.filter(&elem(&1, 1))
|
||||
|
@ -90,7 +93,7 @@ defp resource_search(_, "accounts", query, options) do
|
|||
users: accounts,
|
||||
for: options[:for_user],
|
||||
as: :user,
|
||||
skip_relationships: false
|
||||
embed_relationships: options[:embed_relationships]
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -100,8 +103,7 @@ defp resource_search(_, "statuses", query, options) do
|
|||
StatusView.render("index.json",
|
||||
activities: statuses,
|
||||
for: options[:for_user],
|
||||
as: :activity,
|
||||
skip_relationships: options[:skip_relationships]
|
||||
as: :activity
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper,
|
||||
only: [try_render: 3, add_link_headers: 2, skip_relationships?: 1]
|
||||
only: [try_render: 3, add_link_headers: 2]
|
||||
|
||||
require Ecto.Query
|
||||
|
||||
|
@ -105,7 +105,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
|
||||
`ids` query param is required
|
||||
"""
|
||||
def index(%{assigns: %{user: user}} = conn, %{ids: ids} = params) do
|
||||
def index(%{assigns: %{user: user}} = conn, %{ids: ids} = _params) do
|
||||
limit = 100
|
||||
|
||||
activities =
|
||||
|
@ -117,8 +117,7 @@ def index(%{assigns: %{user: user}} = conn, %{ids: ids} = params) do
|
|||
render(conn, "index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
as: :activity
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -383,8 +382,7 @@ def favourites(%{assigns: %{user: %User{} = user}} = conn, params) do
|
|||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
as: :activity
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -406,8 +404,7 @@ def bookmarks(%{assigns: %{user: user}} = conn, params) do
|
|||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
as: :activity
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper,
|
||||
only: [add_link_headers: 2, add_link_headers: 3, skip_relationships?: 1]
|
||||
only: [add_link_headers: 2, add_link_headers: 3]
|
||||
|
||||
alias Pleroma.Pagination
|
||||
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
|
||||
|
@ -63,8 +63,7 @@ def home(%{assigns: %{user: user}} = conn, params) do
|
|||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
as: :activity
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -88,8 +87,7 @@ def direct(%{assigns: %{user: user}} = conn, params) do
|
|||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
as: :activity
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -125,8 +123,7 @@ def public(%{assigns: %{user: user}} = conn, params) do
|
|||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
as: :activity
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -173,8 +170,7 @@ def hashtag(%{assigns: %{user: user}} = conn, params) do
|
|||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
as: :activity
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -203,8 +199,7 @@ def list(%{assigns: %{user: user}} = conn, %{list_id: id} = params) do
|
|||
render(conn, "index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
as: :activity
|
||||
)
|
||||
else
|
||||
_e -> render_error(conn, :forbidden, "Error.")
|
||||
|
|
|
@ -15,13 +15,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
def render("index.json", %{users: users} = opts) do
|
||||
reading_user = opts[:for]
|
||||
|
||||
# Note: :skip_relationships option is currently intentionally not supported for accounts
|
||||
relationships_opt =
|
||||
cond do
|
||||
Map.has_key?(opts, :relationships) ->
|
||||
opts[:relationships]
|
||||
|
||||
is_nil(reading_user) ->
|
||||
is_nil(reading_user) || !opts[:embed_relationships] ->
|
||||
UserRelationship.view_relationships_option(nil, [])
|
||||
|
||||
true ->
|
||||
|
@ -193,14 +192,14 @@ defp do_render("show.json", %{user: user} = opts) do
|
|||
end)
|
||||
|
||||
relationship =
|
||||
if opts[:skip_relationships] do
|
||||
%{}
|
||||
else
|
||||
if opts[:embed_relationships] do
|
||||
render("relationship.json", %{
|
||||
user: opts[:for],
|
||||
target: user,
|
||||
relationships: opts[:relationships]
|
||||
})
|
||||
else
|
||||
%{}
|
||||
end
|
||||
|
||||
%{
|
||||
|
|
|
@ -33,6 +33,7 @@ def render("show.json", _) do
|
|||
avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit),
|
||||
background_upload_limit: Keyword.get(instance, :background_upload_limit),
|
||||
banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
|
||||
background_image: Keyword.get(instance, :background_image),
|
||||
pleroma: %{
|
||||
metadata: %{
|
||||
features: features(),
|
||||
|
|
|
@ -51,9 +51,7 @@ def render("index.json", %{notifications: notifications, for: reading_user} = op
|
|||
|> Enum.filter(& &1)
|
||||
|> Kernel.++(move_activities_targets)
|
||||
|
||||
UserRelationship.view_relationships_option(reading_user, actors,
|
||||
source_mutes_only: opts[:skip_relationships]
|
||||
)
|
||||
UserRelationship.view_relationships_option(reading_user, actors, subset: :source_mutes)
|
||||
end
|
||||
|
||||
opts =
|
||||
|
@ -83,15 +81,13 @@ def render(
|
|||
|
||||
mastodon_type = Activity.mastodon_notification_type(activity)
|
||||
|
||||
render_opts = %{
|
||||
relationships: opts[:relationships],
|
||||
skip_relationships: opts[:skip_relationships]
|
||||
}
|
||||
# Note: :relationships contain user mutes (needed for :muted flag in :status)
|
||||
status_render_opts = %{relationships: opts[:relationships]}
|
||||
|
||||
with %{id: _} = account <-
|
||||
AccountView.render(
|
||||
"show.json",
|
||||
Map.merge(render_opts, %{user: actor, for: reading_user})
|
||||
%{user: actor, for: reading_user}
|
||||
) do
|
||||
response = %{
|
||||
id: to_string(notification.id),
|
||||
|
@ -105,21 +101,20 @@ def render(
|
|||
|
||||
case mastodon_type do
|
||||
"mention" ->
|
||||
put_status(response, activity, reading_user, render_opts)
|
||||
put_status(response, activity, reading_user, status_render_opts)
|
||||
|
||||
"favourite" ->
|
||||
put_status(response, parent_activity_fn.(), reading_user, render_opts)
|
||||
put_status(response, parent_activity_fn.(), reading_user, status_render_opts)
|
||||
|
||||
"reblog" ->
|
||||
put_status(response, parent_activity_fn.(), reading_user, render_opts)
|
||||
put_status(response, parent_activity_fn.(), reading_user, status_render_opts)
|
||||
|
||||
"move" ->
|
||||
# Note: :skip_relationships option being applied to _account_ rendering (here)
|
||||
put_target(response, activity, reading_user, render_opts)
|
||||
put_target(response, activity, reading_user, %{})
|
||||
|
||||
"pleroma:emoji_reaction" ->
|
||||
response
|
||||
|> put_status(parent_activity_fn.(), reading_user, render_opts)
|
||||
|> put_status(parent_activity_fn.(), reading_user, status_render_opts)
|
||||
|> put_emoji(activity)
|
||||
|
||||
type when type in ["follow", "follow_request"] ->
|
||||
|
|
|
@ -107,9 +107,7 @@ def render("index.json", opts) do
|
|||
|> Enum.map(&get_user(&1.data["actor"], false))
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
UserRelationship.view_relationships_option(reading_user, actors,
|
||||
source_mutes_only: opts[:skip_relationships]
|
||||
)
|
||||
UserRelationship.view_relationships_option(reading_user, actors, subset: :source_mutes)
|
||||
end
|
||||
|
||||
opts =
|
||||
|
@ -162,9 +160,7 @@ def render(
|
|||
account:
|
||||
AccountView.render("show.json", %{
|
||||
user: user,
|
||||
for: opts[:for],
|
||||
relationships: opts[:relationships],
|
||||
skip_relationships: opts[:skip_relationships]
|
||||
for: opts[:for]
|
||||
}),
|
||||
in_reply_to_id: nil,
|
||||
in_reply_to_account_id: nil,
|
||||
|
@ -330,9 +326,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
|||
account:
|
||||
AccountView.render("show.json", %{
|
||||
user: user,
|
||||
for: opts[:for],
|
||||
relationships: opts[:relationships],
|
||||
skip_relationships: opts[:skip_relationships]
|
||||
for: opts[:for]
|
||||
}),
|
||||
in_reply_to_id: reply_to && to_string(reply_to.id),
|
||||
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
|
||||
|
@ -443,27 +437,6 @@ def render("attachment.json", %{attachment: attachment}) do
|
|||
}
|
||||
end
|
||||
|
||||
def render("listen.json", %{activity: %Activity{data: %{"type" => "Listen"}} = activity} = opts) do
|
||||
object = Object.normalize(activity)
|
||||
|
||||
user = get_user(activity.data["actor"])
|
||||
created_at = Utils.to_masto_date(activity.data["published"])
|
||||
|
||||
%{
|
||||
id: activity.id,
|
||||
account: AccountView.render("show.json", %{user: user, for: opts[:for]}),
|
||||
created_at: created_at,
|
||||
title: object.data["title"] |> HTML.strip_tags(),
|
||||
artist: object.data["artist"] |> HTML.strip_tags(),
|
||||
album: object.data["album"] |> HTML.strip_tags(),
|
||||
length: object.data["length"]
|
||||
}
|
||||
end
|
||||
|
||||
def render("listens.json", opts) do
|
||||
safe_render_many(opts.activities, StatusView, "listen.json", opts)
|
||||
end
|
||||
|
||||
def render("context.json", %{activity: activity, activities: activities, user: user}) do
|
||||
%{ancestors: ancestors, descendants: descendants} =
|
||||
activities
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MediaProxy.Invalidation do
|
||||
@moduledoc false
|
||||
|
||||
@callback purge(list(String.t()), map()) :: {:ok, String.t()} | {:error, String.t()}
|
||||
|
||||
alias Pleroma.Config
|
||||
|
||||
@spec purge(list(String.t())) :: {:ok, String.t()} | {:error, String.t()}
|
||||
def purge(urls) do
|
||||
[:media_proxy, :invalidation, :enabled]
|
||||
|> Config.get()
|
||||
|> do_purge(urls)
|
||||
end
|
||||
|
||||
defp do_purge(true, urls) do
|
||||
provider = Config.get([:media_proxy, :invalidation, :provider])
|
||||
options = Config.get(provider)
|
||||
provider.purge(urls, options)
|
||||
end
|
||||
|
||||
defp do_purge(_, _), do: :ok
|
||||
end
|
|
@ -0,0 +1,40 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MediaProxy.Invalidation.Http do
|
||||
@moduledoc false
|
||||
@behaviour Pleroma.Web.MediaProxy.Invalidation
|
||||
|
||||
require Logger
|
||||
|
||||
@impl Pleroma.Web.MediaProxy.Invalidation
|
||||
def purge(urls, opts) do
|
||||
method = Map.get(opts, :method, :purge)
|
||||
headers = Map.get(opts, :headers, [])
|
||||
options = Map.get(opts, :options, [])
|
||||
|
||||
Logger.debug("Running cache purge: #{inspect(urls)}")
|
||||
|
||||
Enum.each(urls, fn url ->
|
||||
with {:error, error} <- do_purge(method, url, headers, options) do
|
||||
Logger.error("Error while cache purge: url - #{url}, error: #{inspect(error)}")
|
||||
end
|
||||
end)
|
||||
|
||||
{:ok, "success"}
|
||||
end
|
||||
|
||||
defp do_purge(method, url, headers, options) do
|
||||
case Pleroma.HTTP.request(method, url, "", headers, options) do
|
||||
{:ok, %{status: status} = env} when 400 <= status and status < 500 ->
|
||||
{:error, env}
|
||||
|
||||
{:error, error} = error ->
|
||||
error
|
||||
|
||||
_ ->
|
||||
{:ok, "success"}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,41 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MediaProxy.Invalidation.Script do
|
||||
@moduledoc false
|
||||
|
||||
@behaviour Pleroma.Web.MediaProxy.Invalidation
|
||||
|
||||
require Logger
|
||||
|
||||
@impl Pleroma.Web.MediaProxy.Invalidation
|
||||
def purge(urls, %{script_path: script_path} = _options) do
|
||||
args =
|
||||
urls
|
||||
|> List.wrap()
|
||||
|> Enum.uniq()
|
||||
|> Enum.join(" ")
|
||||
|
||||
path = Path.expand(script_path)
|
||||
|
||||
Logger.debug("Running cache purge: #{inspect(urls)}, #{path}")
|
||||
|
||||
case do_purge(path, [args]) do
|
||||
{result, exit_status} when exit_status > 0 ->
|
||||
Logger.error("Error while cache purge: #{inspect(result)}")
|
||||
{:error, inspect(result)}
|
||||
|
||||
_ ->
|
||||
{:ok, "success"}
|
||||
end
|
||||
end
|
||||
|
||||
def purge(_, _), do: {:error, "not found script path"}
|
||||
|
||||
defp do_purge(path, args) do
|
||||
System.cmd(path, args)
|
||||
rescue
|
||||
error -> {inspect(error), 1}
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.OAuth.MFAController do
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.OAuth.MFAView do
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.OAuth.Token.CleanWorker do
|
||||
|
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper,
|
||||
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2, skip_relationships?: 1]
|
||||
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
|
||||
|
||||
alias Ecto.Changeset
|
||||
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
|
||||
|
@ -149,8 +149,7 @@ def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do
|
|||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: for_user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
as: :activity
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
|
||||
defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Emoji.Pack
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
||||
plug(
|
||||
Pleroma.Plugs.OAuthScopesPlug,
|
||||
%{scopes: ["write"], admin: true}
|
||||
|
@ -19,39 +21,37 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
|
|||
]
|
||||
)
|
||||
|
||||
plug(
|
||||
:skip_plug,
|
||||
[Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug]
|
||||
when action in [:archive, :show, :list]
|
||||
)
|
||||
@skip_plugs [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug]
|
||||
plug(:skip_plug, @skip_plugs when action in [:archive, :show, :list])
|
||||
|
||||
def remote(conn, %{"url" => url}) do
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation
|
||||
|
||||
def remote(conn, %{url: url}) do
|
||||
with {:ok, packs} <- Pack.list_remote(url) do
|
||||
json(conn, packs)
|
||||
else
|
||||
{:shareable, _} ->
|
||||
{:error, :not_shareable} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "The requested instance does not support sharing emoji packs"})
|
||||
end
|
||||
end
|
||||
|
||||
def list(conn, _params) do
|
||||
def index(conn, _params) do
|
||||
emoji_path =
|
||||
Path.join(
|
||||
Pleroma.Config.get!([:instance, :static_dir]),
|
||||
"emoji"
|
||||
)
|
||||
[:instance, :static_dir]
|
||||
|> Pleroma.Config.get!()
|
||||
|> Path.join("emoji")
|
||||
|
||||
with {:ok, packs} <- Pack.list_local() do
|
||||
json(conn, packs)
|
||||
else
|
||||
{:create_dir, {:error, e}} ->
|
||||
{:error, :create_dir, e} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "Failed to create the emoji pack directory at #{emoji_path}: #{e}"})
|
||||
|
||||
{:ls, {:error, e}} ->
|
||||
{:error, :ls, e} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{
|
||||
|
@ -60,13 +60,13 @@ def list(conn, _params) do
|
|||
end
|
||||
end
|
||||
|
||||
def show(conn, %{"name" => name}) do
|
||||
def show(conn, %{name: name}) do
|
||||
name = String.trim(name)
|
||||
|
||||
with {:ok, pack} <- Pack.show(name) do
|
||||
json(conn, pack)
|
||||
else
|
||||
{:loaded, _} ->
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Pack #{name} does not exist"})
|
||||
|
@ -78,11 +78,11 @@ def show(conn, %{"name" => name}) do
|
|||
end
|
||||
end
|
||||
|
||||
def archive(conn, %{"name" => name}) do
|
||||
def archive(conn, %{name: name}) do
|
||||
with {:ok, archive} <- Pack.get_archive(name) do
|
||||
send_download(conn, {:binary, archive}, filename: "#{name}.zip")
|
||||
else
|
||||
{:can_download?, _} ->
|
||||
{:error, :cant_download} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{
|
||||
|
@ -90,23 +90,23 @@ def archive(conn, %{"name" => name}) do
|
|||
"Pack #{name} cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing"
|
||||
})
|
||||
|
||||
{:exists?, _} ->
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Pack #{name} does not exist"})
|
||||
end
|
||||
end
|
||||
|
||||
def download(conn, %{"url" => url, "name" => name} = params) do
|
||||
with :ok <- Pack.download(name, url, params["as"]) do
|
||||
def download(%{body_params: %{url: url, name: name} = params} = conn, _) do
|
||||
with {:ok, _pack} <- Pack.download(name, url, params[:as]) do
|
||||
json(conn, "ok")
|
||||
else
|
||||
{:shareable, _} ->
|
||||
{:error, :not_shareable} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "The requested instance does not support sharing emoji packs"})
|
||||
|
||||
{:checksum, _} ->
|
||||
{:error, :imvalid_checksum} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"})
|
||||
|
@ -118,10 +118,10 @@ def download(conn, %{"url" => url, "name" => name} = params) do
|
|||
end
|
||||
end
|
||||
|
||||
def create(conn, %{"name" => name}) do
|
||||
def create(conn, %{name: name}) do
|
||||
name = String.trim(name)
|
||||
|
||||
with :ok <- Pack.create(name) do
|
||||
with {:ok, _pack} <- Pack.create(name) do
|
||||
json(conn, "ok")
|
||||
else
|
||||
{:error, :eexist} ->
|
||||
|
@ -143,7 +143,7 @@ def create(conn, %{"name" => name}) do
|
|||
end
|
||||
end
|
||||
|
||||
def delete(conn, %{"name" => name}) do
|
||||
def delete(conn, %{name: name}) do
|
||||
name = String.trim(name)
|
||||
|
||||
with {:ok, deleted} when deleted != [] <- Pack.delete(name) do
|
||||
|
@ -166,11 +166,11 @@ def delete(conn, %{"name" => name}) do
|
|||
end
|
||||
end
|
||||
|
||||
def update(conn, %{"name" => name, "metadata" => metadata}) do
|
||||
def update(%{body_params: %{metadata: metadata}} = conn, %{name: name}) do
|
||||
with {:ok, pack} <- Pack.update_metadata(name, metadata) do
|
||||
json(conn, pack.pack)
|
||||
else
|
||||
{:has_all_files?, _} ->
|
||||
{:error, :incomplete} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "The fallback archive does not have all files specified in pack.json"})
|
||||
|
@ -184,19 +184,19 @@ def update(conn, %{"name" => name, "metadata" => metadata}) do
|
|||
end
|
||||
end
|
||||
|
||||
def add_file(conn, %{"name" => name} = params) do
|
||||
filename = params["filename"] || get_filename(params["file"])
|
||||
shortcode = params["shortcode"] || Path.basename(filename, Path.extname(filename))
|
||||
def add_file(%{body_params: params} = conn, %{name: name}) do
|
||||
filename = params[:filename] || get_filename(params[:file])
|
||||
shortcode = params[:shortcode] || Path.basename(filename, Path.extname(filename))
|
||||
|
||||
with {:ok, pack} <- Pack.add_file(name, shortcode, filename, params["file"]) do
|
||||
with {:ok, pack} <- Pack.add_file(name, shortcode, filename, params[:file]) do
|
||||
json(conn, pack.files)
|
||||
else
|
||||
{:exists, _} ->
|
||||
{:error, :already_exists} ->
|
||||
conn
|
||||
|> put_status(:conflict)
|
||||
|> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"})
|
||||
|
||||
{:loaded, _} ->
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "pack \"#{name}\" is not found"})
|
||||
|
@ -215,20 +215,20 @@ def add_file(conn, %{"name" => name} = params) do
|
|||
end
|
||||
end
|
||||
|
||||
def update_file(conn, %{"name" => name, "shortcode" => shortcode} = params) do
|
||||
new_shortcode = params["new_shortcode"]
|
||||
new_filename = params["new_filename"]
|
||||
force = params["force"] == true
|
||||
def update_file(%{body_params: %{shortcode: shortcode} = params} = conn, %{name: name}) do
|
||||
new_shortcode = params[:new_shortcode]
|
||||
new_filename = params[:new_filename]
|
||||
force = params[:force]
|
||||
|
||||
with {:ok, pack} <- Pack.update_file(name, shortcode, new_shortcode, new_filename, force) do
|
||||
json(conn, pack.files)
|
||||
else
|
||||
{:exists, _} ->
|
||||
{:error, :doesnt_exist} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "Emoji \"#{shortcode}\" does not exist"})
|
||||
|
||||
{:not_used, _} ->
|
||||
{:error, :already_exists} ->
|
||||
conn
|
||||
|> put_status(:conflict)
|
||||
|> json(%{
|
||||
|
@ -236,7 +236,7 @@ def update_file(conn, %{"name" => name, "shortcode" => shortcode} = params) do
|
|||
"New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option"
|
||||
})
|
||||
|
||||
{:loaded, _} ->
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "pack \"#{name}\" is not found"})
|
||||
|
@ -255,16 +255,16 @@ def update_file(conn, %{"name" => name, "shortcode" => shortcode} = params) do
|
|||
end
|
||||
end
|
||||
|
||||
def delete_file(conn, %{"name" => name, "shortcode" => shortcode}) do
|
||||
def delete_file(conn, %{name: name, shortcode: shortcode}) do
|
||||
with {:ok, pack} <- Pack.delete_file(name, shortcode) do
|
||||
json(conn, pack.files)
|
||||
else
|
||||
{:exists, _} ->
|
||||
{:error, :doesnt_exist} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "Emoji \"#{shortcode}\" does not exist"})
|
||||
|
||||
{:loaded, _} ->
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "pack \"#{name}\" is not found"})
|
|
@ -9,16 +9,19 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action == :show)
|
||||
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action != :show)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaMascotOperation
|
||||
|
||||
@doc "GET /api/v1/pleroma/mascot"
|
||||
def show(%{assigns: %{user: user}} = conn, _params) do
|
||||
json(conn, User.get_mascot(user))
|
||||
end
|
||||
|
||||
@doc "PUT /api/v1/pleroma/mascot"
|
||||
def update(%{assigns: %{user: user}} = conn, %{"file" => file}) do
|
||||
def update(%{assigns: %{user: user}, body_params: %{file: file}} = conn, _) do
|
||||
with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)),
|
||||
# Reject if not an image
|
||||
%{type: "image"} = attachment <- render_attachment(object) do
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
|
||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Conversation.Participation
|
||||
|
@ -69,7 +69,12 @@ def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}
|
|||
%{
|
||||
name: emoji,
|
||||
count: length(users),
|
||||
accounts: AccountView.render("index.json", %{users: users, for: user, as: :user}),
|
||||
accounts:
|
||||
AccountView.render("index.json", %{
|
||||
users: users,
|
||||
for: user,
|
||||
as: :user
|
||||
}),
|
||||
me: !!(user && user.ap_id in user_ap_ids)
|
||||
}
|
||||
end
|
||||
|
@ -145,8 +150,7 @@ def conversation_statuses(
|
|||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
as: :activity
|
||||
)
|
||||
else
|
||||
_error ->
|
||||
|
@ -201,7 +205,7 @@ def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{"id" => notif
|
|||
end
|
||||
end
|
||||
|
||||
def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{"max_id" => max_id} = params) do
|
||||
def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do
|
||||
with notifications <- Notification.set_read_up_to(user, max_id) do
|
||||
notifications = Enum.take(notifications, 80)
|
||||
|
||||
|
@ -209,8 +213,7 @@ def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{"max_id" => m
|
|||
|> put_view(NotificationView)
|
||||
|> render("index.json",
|
||||
notifications: notifications,
|
||||
for: user,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
for: user
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,34 +5,27 @@
|
|||
defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, fetch_integer_param: 2]
|
||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
||||
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["read"], fallback: :proceed_unauthenticated} when action == :user_scrobbles
|
||||
%{scopes: ["read"], fallback: :proceed_unauthenticated} when action == :index
|
||||
)
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["write"]} when action != :user_scrobbles)
|
||||
plug(OAuthScopesPlug, %{scopes: ["write"]} when action == :create)
|
||||
|
||||
def new_scrobble(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do
|
||||
params =
|
||||
if !params["length"] do
|
||||
params
|
||||
else
|
||||
params
|
||||
|> Map.put("length", fetch_integer_param(params, "length"))
|
||||
end
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaScrobbleOperation
|
||||
|
||||
def create(%{assigns: %{user: user}, body_params: params} = conn, _) do
|
||||
with {:ok, activity} <- CommonAPI.listen(user, params) do
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> render("listen.json", %{activity: activity, for: user})
|
||||
render(conn, "show.json", activity: activity, for: user)
|
||||
else
|
||||
{:error, message} ->
|
||||
conn
|
||||
|
@ -41,16 +34,18 @@ def new_scrobble(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do
|
|||
end
|
||||
end
|
||||
|
||||
def user_scrobbles(%{assigns: %{user: reading_user}} = conn, params) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do
|
||||
params = Map.put(params, "type", ["Listen"])
|
||||
def index(%{assigns: %{user: reading_user}} = conn, %{id: id} = params) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(id, for: reading_user) do
|
||||
params =
|
||||
params
|
||||
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|
||||
|> Map.put("type", ["Listen"])
|
||||
|
||||
activities = ActivityPub.fetch_user_abstract_activities(user, reading_user, params)
|
||||
|
||||
conn
|
||||
|> add_link_headers(activities)
|
||||
|> put_view(StatusView)
|
||||
|> render("listens.json", %{
|
||||
|> render("index.json", %{
|
||||
activities: activities,
|
||||
for: reading_user,
|
||||
as: :activity
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationController do
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.PleromaAPI.ScrobbleView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.HTML
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
alias Pleroma.Web.MastodonAPI.AccountView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
def render("show.json", %{activity: %Activity{data: %{"type" => "Listen"}} = activity} = opts) do
|
||||
object = Object.normalize(activity)
|
||||
|
||||
user = StatusView.get_user(activity.data["actor"])
|
||||
created_at = Utils.to_masto_date(activity.data["published"])
|
||||
|
||||
%{
|
||||
id: activity.id,
|
||||
account: AccountView.render("show.json", %{user: user, for: opts[:for]}),
|
||||
created_at: created_at,
|
||||
title: object.data["title"] |> HTML.strip_tags(),
|
||||
artist: object.data["artist"] |> HTML.strip_tags(),
|
||||
album: object.data["album"] |> HTML.strip_tags(),
|
||||
length: object.data["length"]
|
||||
}
|
||||
end
|
||||
|
||||
def render("index.json", opts) do
|
||||
safe_render_many(opts.activities, __MODULE__, "show.json", opts)
|
||||
end
|
||||
end
|
|
@ -216,24 +216,25 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/packs" do
|
||||
pipe_through(:admin_api)
|
||||
|
||||
get("/import", EmojiAPIController, :import_from_filesystem)
|
||||
get("/remote", EmojiAPIController, :remote)
|
||||
post("/download", EmojiAPIController, :download)
|
||||
get("/import", EmojiPackController, :import_from_filesystem)
|
||||
get("/remote", EmojiPackController, :remote)
|
||||
post("/download", EmojiPackController, :download)
|
||||
|
||||
post("/:name", EmojiAPIController, :create)
|
||||
patch("/:name", EmojiAPIController, :update)
|
||||
delete("/:name", EmojiAPIController, :delete)
|
||||
post("/:name", EmojiPackController, :create)
|
||||
patch("/:name", EmojiPackController, :update)
|
||||
delete("/:name", EmojiPackController, :delete)
|
||||
|
||||
post("/:name/files", EmojiAPIController, :add_file)
|
||||
patch("/:name/files", EmojiAPIController, :update_file)
|
||||
delete("/:name/files", EmojiAPIController, :delete_file)
|
||||
post("/:name/files", EmojiPackController, :add_file)
|
||||
patch("/:name/files", EmojiPackController, :update_file)
|
||||
delete("/:name/files", EmojiPackController, :delete_file)
|
||||
end
|
||||
|
||||
# Pack info / downloading
|
||||
scope "/packs" do
|
||||
get("/", EmojiAPIController, :list)
|
||||
get("/:name", EmojiAPIController, :show)
|
||||
get("/:name/archive", EmojiAPIController, :archive)
|
||||
pipe_through(:api)
|
||||
get("/", EmojiPackController, :index)
|
||||
get("/:name", EmojiPackController, :show)
|
||||
get("/:name/archive", EmojiPackController, :archive)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -325,7 +326,7 @@ defmodule Pleroma.Web.Router do
|
|||
get("/mascot", MascotController, :show)
|
||||
put("/mascot", MascotController, :update)
|
||||
|
||||
post("/scrobble", ScrobbleController, :new_scrobble)
|
||||
post("/scrobble", ScrobbleController, :create)
|
||||
end
|
||||
|
||||
scope [] do
|
||||
|
@ -345,7 +346,7 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
|
||||
pipe_through(:api)
|
||||
get("/accounts/:id/scrobbles", ScrobbleController, :user_scrobbles)
|
||||
get("/accounts/:id/scrobbles", ScrobbleController, :index)
|
||||
end
|
||||
|
||||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||
|
@ -403,6 +404,7 @@ defmodule Pleroma.Web.Router do
|
|||
post("/markers", MarkerController, :upsert)
|
||||
|
||||
post("/media", MediaController, :create)
|
||||
get("/media/:id", MediaController, :show)
|
||||
put("/media/:id", MediaController, :update)
|
||||
|
||||
get("/notifications", NotificationController, :index)
|
||||
|
@ -497,6 +499,8 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/api/v2", Pleroma.Web.MastodonAPI do
|
||||
pipe_through(:api)
|
||||
get("/search", SearchController, :search2)
|
||||
|
||||
post("/media", MediaController, :create2)
|
||||
end
|
||||
|
||||
scope "/api", Pleroma.Web do
|
||||
|
|
|
@ -27,8 +27,20 @@ def perform(
|
|||
|
||||
uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
|
||||
|
||||
prefix =
|
||||
case Pleroma.Config.get([Pleroma.Upload, :base_url]) do
|
||||
nil -> "media"
|
||||
_ -> ""
|
||||
end
|
||||
|
||||
base_url =
|
||||
String.trim_trailing(
|
||||
Pleroma.Config.get([Pleroma.Upload, :base_url], Pleroma.Web.base_url()),
|
||||
"/"
|
||||
)
|
||||
|
||||
# find all objects for copies of the attachments, name and actor doesn't matter here
|
||||
delete_ids =
|
||||
object_ids_and_hrefs =
|
||||
from(o in Object,
|
||||
where:
|
||||
fragment(
|
||||
|
@ -67,29 +79,28 @@ def perform(
|
|||
|> Enum.map(fn {href, %{id: id, count: count}} ->
|
||||
# only delete files that have single instance
|
||||
with 1 <- count do
|
||||
prefix =
|
||||
case Pleroma.Config.get([Pleroma.Upload, :base_url]) do
|
||||
nil -> "media"
|
||||
_ -> ""
|
||||
end
|
||||
href
|
||||
|> String.trim_leading("#{base_url}/#{prefix}")
|
||||
|> uploader.delete_file()
|
||||
|
||||
base_url =
|
||||
String.trim_trailing(
|
||||
Pleroma.Config.get([Pleroma.Upload, :base_url], Pleroma.Web.base_url()),
|
||||
"/"
|
||||
)
|
||||
|
||||
file_path = String.trim_leading(href, "#{base_url}/#{prefix}")
|
||||
|
||||
uploader.delete_file(file_path)
|
||||
{id, href}
|
||||
else
|
||||
_ -> {id, nil}
|
||||
end
|
||||
|
||||
id
|
||||
end)
|
||||
|
||||
from(o in Object, where: o.id in ^delete_ids)
|
||||
object_ids = Enum.map(object_ids_and_hrefs, fn {id, _} -> id end)
|
||||
|
||||
from(o in Object, where: o.id in ^object_ids)
|
||||
|> Repo.delete_all()
|
||||
|
||||
object_ids_and_hrefs
|
||||
|> Enum.filter(fn {_, href} -> not is_nil(href) end)
|
||||
|> Enum.map(&elem(&1, 1))
|
||||
|> Pleroma.Web.MediaProxy.Invalidation.purge()
|
||||
|
||||
{:ok, :success}
|
||||
end
|
||||
|
||||
def perform(%{"op" => "cleanup_attachments", "object" => _object}, _job), do: :ok
|
||||
def perform(%{"op" => "cleanup_attachments", "object" => _object}, _job), do: {:ok, :skip}
|
||||
end
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -155,7 +155,7 @@ defp deps do
|
|||
{:credo, "~> 1.1.0", only: [:dev, :test], runtime: false},
|
||||
{:mock, "~> 0.3.3", only: :test},
|
||||
{:crypt,
|
||||
git: "https://github.com/msantos/crypt", ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"},
|
||||
git: "https://github.com/msantos/crypt", ref: "f63a705f92c26955977ee62a313012e309a4d77a"},
|
||||
{:cors_plug, "~> 1.5"},
|
||||
{:ex_doc, "~> 0.21", only: :dev, runtime: false},
|
||||
{:web_push_encryption, "~> 0.2.1"},
|
||||
|
|
6
mix.lock
6
mix.lock
|
@ -21,13 +21,13 @@
|
|||
"cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"},
|
||||
"credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0bbd3222607ccaaac5c0340f7f525c627ae4d7aee6c8c8c108922620c5b6446"},
|
||||
"crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
|
||||
"crypt": {:git, "https://github.com/msantos/crypt", "f63a705f92c26955977ee62a313012e309a4d77a", [ref: "f63a705f92c26955977ee62a313012e309a4d77a"]},
|
||||
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
|
||||
"db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "2b02ece62d9f983fcd40954e443b7d9e6589664380e5546b2b9b523cd0fb59e1"},
|
||||
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
|
||||
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
|
||||
"earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
|
||||
"ecto": {:hex, :ecto, "3.4.0", "a7a83ab8359bf816ce729e5e65981ce25b9fc5adfc89c2ea3980f4fed0bfd7c1", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "5eed18252f5b5bbadec56a24112b531343507dbe046273133176b12190ce19cc"},
|
||||
"ecto": {:hex, :ecto, "3.4.4", "a2c881e80dc756d648197ae0d936216c0308370332c5e77a2325a10293eef845", [: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", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4bd3ad62abc3b21fb629f0f7a3dab23a192fca837d257dd08449fba7373561"},
|
||||
"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_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"},
|
||||
"eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"},
|
||||
|
@ -60,7 +60,7 @@
|
|||
"httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"},
|
||||
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
|
||||
"inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
|
||||
"jason": {:hex, :jason, "1.2.0", "10043418c42d2493d0ee212d3fddd25d7ffe484380afad769a0a38795938e448", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "116747dbe057794c3a3e4e143b7c8390b29f634e16c78a7f59ba75bfa6852e7f"},
|
||||
"jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"},
|
||||
"joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"},
|
||||
"jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
|
||||
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
|
||||
|
|
|
@ -0,0 +1,578 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-05-15 09:37+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: nl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Translate Toolkit 2.5.1\n"
|
||||
|
||||
## This file is a PO Template file.
|
||||
##
|
||||
## `msgid`s here are often extracted from source code.
|
||||
## Add new translations manually only if they're dynamic
|
||||
## translations that can't be statically extracted.
|
||||
##
|
||||
## Run `mix gettext.extract` to bring this file up to
|
||||
## date. Leave `msgstr`s empty as changing them here as no
|
||||
## effect: edit them in PO (`.po`) files instead.
|
||||
## From Ecto.Changeset.cast/4
|
||||
msgid "can't be blank"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.unique_constraint/3
|
||||
msgid "has already been taken"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.put_change/3
|
||||
msgid "is invalid"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_format/3
|
||||
msgid "has invalid format"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_subset/3
|
||||
msgid "has an invalid entry"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_exclusion/3
|
||||
msgid "is reserved"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_confirmation/3
|
||||
msgid "does not match confirmation"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.no_assoc_constraint/3
|
||||
msgid "is still associated with this entry"
|
||||
msgstr ""
|
||||
|
||||
msgid "are still associated with this entry"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_length/3
|
||||
msgid "should be %{count} character(s)"
|
||||
msgid_plural "should be %{count} character(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "should have %{count} item(s)"
|
||||
msgid_plural "should have %{count} item(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "should be at least %{count} character(s)"
|
||||
msgid_plural "should be at least %{count} character(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "should have at least %{count} item(s)"
|
||||
msgid_plural "should have at least %{count} item(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "should be at most %{count} character(s)"
|
||||
msgid_plural "should be at most %{count} character(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "should have at most %{count} item(s)"
|
||||
msgid_plural "should have at most %{count} item(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
## From Ecto.Changeset.validate_number/3
|
||||
msgid "must be less than %{number}"
|
||||
msgstr ""
|
||||
|
||||
msgid "must be greater than %{number}"
|
||||
msgstr ""
|
||||
|
||||
msgid "must be less than or equal to %{number}"
|
||||
msgstr ""
|
||||
|
||||
msgid "must be greater than or equal to %{number}"
|
||||
msgstr ""
|
||||
|
||||
msgid "must be equal to %{number}"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:421
|
||||
#, elixir-format
|
||||
msgid "Account not found"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:249
|
||||
#, elixir-format
|
||||
msgid "Already voted"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:360
|
||||
#, elixir-format
|
||||
msgid "Bad request"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425
|
||||
#, elixir-format
|
||||
msgid "Can't delete object"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:196
|
||||
#, elixir-format
|
||||
msgid "Can't delete this post"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/controller_helper.ex:95
|
||||
#: lib/pleroma/web/controller_helper.ex:101
|
||||
#, elixir-format
|
||||
msgid "Can't display this activity"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:227
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:254
|
||||
#, elixir-format
|
||||
msgid "Can't find user"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:114
|
||||
#, elixir-format
|
||||
msgid "Can't get favorites"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:437
|
||||
#, elixir-format
|
||||
msgid "Can't like object"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/utils.ex:556
|
||||
#, elixir-format
|
||||
msgid "Cannot post an empty status without attachments"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/utils.ex:504
|
||||
#, elixir-format
|
||||
msgid "Comment must be up to %{max_size} characters"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/config/config_db.ex:222
|
||||
#, elixir-format
|
||||
msgid "Config with params %{params} not found"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:95
|
||||
#, elixir-format
|
||||
msgid "Could not delete"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:141
|
||||
#, elixir-format
|
||||
msgid "Could not favorite"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:370
|
||||
#, elixir-format
|
||||
msgid "Could not pin"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:112
|
||||
#, elixir-format
|
||||
msgid "Could not repeat"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:188
|
||||
#, elixir-format
|
||||
msgid "Could not unfavorite"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:380
|
||||
#, elixir-format
|
||||
msgid "Could not unpin"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:126
|
||||
#, elixir-format
|
||||
msgid "Could not unrepeat"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:428
|
||||
#: lib/pleroma/web/common_api/common_api.ex:437
|
||||
#, elixir-format
|
||||
msgid "Could not update state"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202
|
||||
#, elixir-format
|
||||
msgid "Error."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:106
|
||||
#, elixir-format
|
||||
msgid "Invalid CAPTCHA"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:569
|
||||
#, elixir-format
|
||||
msgid "Invalid credentials"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
|
||||
#, elixir-format
|
||||
msgid "Invalid credentials."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:265
|
||||
#, elixir-format
|
||||
msgid "Invalid indices"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1147
|
||||
#, elixir-format
|
||||
msgid "Invalid parameters"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/utils.ex:411
|
||||
#, elixir-format
|
||||
msgid "Invalid password."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:187
|
||||
#, elixir-format
|
||||
msgid "Invalid request"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:109
|
||||
#, elixir-format
|
||||
msgid "Kocaptcha service unavailable"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:113
|
||||
#, elixir-format
|
||||
msgid "Missing parameters"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/utils.ex:540
|
||||
#, elixir-format
|
||||
msgid "No such conversation"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:439
|
||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:465 lib/pleroma/web/admin_api/admin_api_controller.ex:507
|
||||
#, elixir-format
|
||||
msgid "No such permission_group"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/plugs/uploaded_media.ex:74
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:485 lib/pleroma/web/admin_api/admin_api_controller.ex:1135
|
||||
#: lib/pleroma/web/feed/user_controller.ex:73 lib/pleroma/web/ostatus/ostatus_controller.ex:143
|
||||
#, elixir-format
|
||||
msgid "Not found"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:241
|
||||
#, elixir-format
|
||||
msgid "Poll's author can't vote"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
|
||||
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
|
||||
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:290
|
||||
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
|
||||
#, elixir-format
|
||||
msgid "Record not found"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1153
|
||||
#: lib/pleroma/web/feed/user_controller.ex:79 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:32
|
||||
#: lib/pleroma/web/ostatus/ostatus_controller.ex:149
|
||||
#, elixir-format
|
||||
msgid "Something went wrong"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/activity_draft.ex:107
|
||||
#, elixir-format
|
||||
msgid "The message visibility must be direct"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/utils.ex:566
|
||||
#, elixir-format
|
||||
msgid "The status is over the character limit"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
|
||||
#, elixir-format
|
||||
msgid "This resource requires authentication."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
|
||||
#, elixir-format
|
||||
msgid "Throttled"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:266
|
||||
#, elixir-format
|
||||
msgid "Too many choices"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442
|
||||
#, elixir-format
|
||||
msgid "Unhandled activity type"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:536
|
||||
#, elixir-format
|
||||
msgid "You can't revoke your own admin status."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:218
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:309
|
||||
#, elixir-format
|
||||
msgid "Your account is currently disabled"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:180
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:332
|
||||
#, elixir-format
|
||||
msgid "Your login is missing a confirmed e-mail address"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389
|
||||
#, elixir-format
|
||||
msgid "can't read inbox of %{nickname} as %{as_nickname}"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472
|
||||
#, elixir-format
|
||||
msgid "can't update outbox of %{nickname} as %{as_nickname}"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:388
|
||||
#, elixir-format
|
||||
msgid "conversation is already muted"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491
|
||||
#, elixir-format
|
||||
msgid "error"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:29
|
||||
#, elixir-format
|
||||
msgid "mascots can only be images"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:60
|
||||
#, elixir-format
|
||||
msgid "not found"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:395
|
||||
#, elixir-format
|
||||
msgid "Bad OAuth request."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:115
|
||||
#, elixir-format
|
||||
msgid "CAPTCHA already used"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:112
|
||||
#, elixir-format
|
||||
msgid "CAPTCHA expired"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/plugs/uploaded_media.ex:55
|
||||
#, elixir-format
|
||||
msgid "Failed"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:411
|
||||
#, elixir-format
|
||||
msgid "Failed to authenticate: %{message}."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:442
|
||||
#, elixir-format
|
||||
msgid "Failed to set up user account."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/plugs/oauth_scopes_plug.ex:38
|
||||
#, elixir-format
|
||||
msgid "Insufficient permissions: %{permissions}."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/plugs/uploaded_media.ex:94
|
||||
#, elixir-format
|
||||
msgid "Internal Error"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/oauth/fallback_controller.ex:22
|
||||
#: lib/pleroma/web/oauth/fallback_controller.ex:29
|
||||
#, elixir-format
|
||||
msgid "Invalid Username/Password"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:118
|
||||
#, elixir-format
|
||||
msgid "Invalid answer data"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128
|
||||
#, elixir-format
|
||||
msgid "Nodeinfo schema version not handled"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:169
|
||||
#, elixir-format
|
||||
msgid "This action is outside the authorized scopes"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/oauth/fallback_controller.ex:14
|
||||
#, elixir-format
|
||||
msgid "Unknown error, please check the details and try again."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:116
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:155
|
||||
#, elixir-format
|
||||
msgid "Unlisted redirect_uri."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:391
|
||||
#, elixir-format
|
||||
msgid "Unsupported OAuth provider: %{provider}."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/uploaders/uploader.ex:72
|
||||
#, elixir-format
|
||||
msgid "Uploader callback timeout"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/uploader_controller.ex:23
|
||||
#, elixir-format
|
||||
msgid "bad request"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:103
|
||||
#, elixir-format
|
||||
msgid "CAPTCHA Error"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:200
|
||||
#, elixir-format
|
||||
msgid "Could not add reaction emoji"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:211
|
||||
#, elixir-format
|
||||
msgid "Could not remove reaction emoji"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:129
|
||||
#, elixir-format
|
||||
msgid "Invalid CAPTCHA (Missing parameter: %{name})"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
|
||||
#, elixir-format
|
||||
msgid "List not found"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:124
|
||||
#, elixir-format
|
||||
msgid "Missing parameter: %{name}"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:207
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:322
|
||||
#, elixir-format
|
||||
msgid "Password reset is required"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/tests/auth_test_controller.ex:9
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/admin_api_controller.ex:6
|
||||
#: lib/pleroma/web/controller_helper.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:2 lib/pleroma/web/masto_fe_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6
|
||||
#: 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/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:8 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/timeline_controller.ex:6 lib/pleroma/web/media_proxy/media_proxy_controller.ex:6
|
||||
#: lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6 lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6
|
||||
#: lib/pleroma/web/oauth/fallback_controller.ex:6 lib/pleroma/web/oauth/mfa_controller.ex:10
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:6 lib/pleroma/web/ostatus/ostatus_controller.ex:6
|
||||
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:2
|
||||
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex:6
|
||||
#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6
|
||||
#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_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/twitter_api/twitter_api_controller.ex:6
|
||||
#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6
|
||||
#, elixir-format
|
||||
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
|
||||
#, elixir-format
|
||||
msgid "Two-factor authentication enabled, you must use a access token."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210
|
||||
#, elixir-format
|
||||
msgid "Unexpected error occurred while adding file to pack."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138
|
||||
#, elixir-format
|
||||
msgid "Unexpected error occurred while creating pack."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278
|
||||
#, elixir-format
|
||||
msgid "Unexpected error occurred while removing file from pack."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250
|
||||
#, elixir-format
|
||||
msgid "Unexpected error occurred while updating file in pack."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179
|
||||
#, elixir-format
|
||||
msgid "Unexpected error occurred while updating pack metadata."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/plugs/user_is_admin_plug.ex:40
|
||||
#, elixir-format
|
||||
msgid "User is not an admin or OAuth admin scope is not granted."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
|
||||
#, elixir-format
|
||||
msgid "Web push subscription is disabled on this Pleroma instance"
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/admin_api/admin_api_controller.ex:502
|
||||
#, elixir-format
|
||||
msgid "You can't revoke your own admin/moderator status."
|
||||
msgstr ""
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:105
|
||||
#, elixir-format
|
||||
msgid "authorization required for timeline view"
|
||||
msgstr ""
|
|
@ -3,8 +3,8 @@ msgstr ""
|
|||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-05-13 16:37+0000\n"
|
||||
"PO-Revision-Date: 2020-05-14 14:37+0000\n"
|
||||
"Last-Translator: Michał Sidor <pleromeme@meekchopp.es>\n"
|
||||
"PO-Revision-Date: 2020-05-16 17:13+0000\n"
|
||||
"Last-Translator: Jędrzej Tomaszewski <jederow@hotmail.com>\n"
|
||||
"Language-Team: Polish <https://translate.pleroma.social/projects/pleroma/"
|
||||
"pleroma/pl/>\n"
|
||||
"Language: pl\n"
|
||||
|
@ -46,7 +46,7 @@ msgstr "ma niepoprawny wpis"
|
|||
|
||||
## From Ecto.Changeset.validate_exclusion/3
|
||||
msgid "is reserved"
|
||||
msgstr ""
|
||||
msgstr "jest zarezerwowany"
|
||||
|
||||
## From Ecto.Changeset.validate_confirmation/3
|
||||
msgid "does not match confirmation"
|
||||
|
@ -54,17 +54,17 @@ msgstr ""
|
|||
|
||||
## From Ecto.Changeset.no_assoc_constraint/3
|
||||
msgid "is still associated with this entry"
|
||||
msgstr ""
|
||||
msgstr "jest wciąż powiązane z tym wpisem"
|
||||
|
||||
msgid "are still associated with this entry"
|
||||
msgstr ""
|
||||
msgstr "są wciąż powiązane z tym wpisem"
|
||||
|
||||
## From Ecto.Changeset.validate_length/3
|
||||
msgid "should be %{count} character(s)"
|
||||
msgid_plural "should be %{count} character(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[2] ""
|
||||
msgstr[0] "powinno mieć %{count} znak"
|
||||
msgstr[1] "powinno mieć %{count} znaki"
|
||||
msgstr[2] "powinno mieć %{count} znaków"
|
||||
|
||||
msgid "should have %{count} item(s)"
|
||||
msgid_plural "should have %{count} item(s)"
|
||||
|
|
|
@ -1 +1 @@
|
|||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/vendors~app.18fea621d430000acc27.css rel=stylesheet><link href=/static/css/app.613cef07981cd95ccceb.css rel=stylesheet><link href=/static/fontello.1588947937982.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.561a1c605d1dfb0e6f74.js></script><script type=text/javascript src=/static/js/app.996428ccaaaa7f28cb8d.js></script></body></html>
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/vendors~app.18fea621d430000acc27.css rel=stylesheet><link href=/static/css/app.613cef07981cd95ccceb.css rel=stylesheet><link href=/static/fontello.1589385935077.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.561a1c605d1dfb0e6f74.js></script><script type=text/javascript src=/static/js/app.838ffa9aecf210c7d744.js></script></body></html>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -4,103 +4,69 @@
|
|||
http://jedwatson.github.io/classnames
|
||||
*/
|
||||
|
||||
/*!
|
||||
* escape-html
|
||||
* Copyright(c) 2012-2013 TJ Holowaychuk
|
||||
* Copyright(c) 2015 Andreas Lubbe
|
||||
* Copyright(c) 2015 Tiancheng "Timothy" Gu
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/*!
|
||||
* wavesurfer.js 3.3.1 (2020-01-14)
|
||||
* https://github.com/katspaugh/wavesurfer.js
|
||||
* @license BSD-3-Clause
|
||||
*/
|
||||
|
||||
/*!****************************************!*\
|
||||
!*** ./node_modules/debounce/index.js ***!
|
||||
\****************************************/
|
||||
|
||||
/*! no static exports found */
|
||||
|
||||
/*!***********************************!*\
|
||||
!*** ./src/drawer.canvasentry.js ***!
|
||||
\***********************************/
|
||||
|
||||
/*! ./util/style */
|
||||
|
||||
/*! ./util/get-id */
|
||||
|
||||
/*!***********************!*\
|
||||
!*** ./src/drawer.js ***!
|
||||
\***********************/
|
||||
|
||||
/*! ./util */
|
||||
|
||||
/*!***********************************!*\
|
||||
!*** ./src/drawer.multicanvas.js ***!
|
||||
\***********************************/
|
||||
/*! ./ajax */
|
||||
|
||||
/*! ./drawer */
|
||||
|
||||
/*! ./drawer.canvasentry */
|
||||
|
||||
/*!**************************************!*\
|
||||
!*** ./src/mediaelement-webaudio.js ***!
|
||||
\**************************************/
|
||||
/*! ./drawer.multicanvas */
|
||||
|
||||
/*! ./mediaelement */
|
||||
/*! ./extend */
|
||||
|
||||
/*!*****************************!*\
|
||||
!*** ./src/mediaelement.js ***!
|
||||
\*****************************/
|
||||
/*! ./fetch */
|
||||
|
||||
/*! ./webaudio */
|
||||
|
||||
/*!**************************!*\
|
||||
!*** ./src/peakcache.js ***!
|
||||
\**************************/
|
||||
|
||||
/*!**************************!*\
|
||||
!*** ./src/util/ajax.js ***!
|
||||
\**************************/
|
||||
|
||||
/*! ./observer */
|
||||
|
||||
/*!****************************!*\
|
||||
!*** ./src/util/extend.js ***!
|
||||
\****************************/
|
||||
|
||||
/*!***************************!*\
|
||||
!*** ./src/util/fetch.js ***!
|
||||
\***************************/
|
||||
|
||||
/*!***************************!*\
|
||||
!*** ./src/util/frame.js ***!
|
||||
\***************************/
|
||||
|
||||
/*! ./request-animation-frame */
|
||||
|
||||
/*!****************************!*\
|
||||
!*** ./src/util/get-id.js ***!
|
||||
\****************************/
|
||||
|
||||
/*!***************************!*\
|
||||
!*** ./src/util/index.js ***!
|
||||
\***************************/
|
||||
|
||||
/*! ./ajax */
|
||||
/*! ./frame */
|
||||
|
||||
/*! ./get-id */
|
||||
|
||||
/*! ./max */
|
||||
|
||||
/*! ./mediaelement */
|
||||
|
||||
/*! ./mediaelement-webaudio */
|
||||
|
||||
/*! ./min */
|
||||
|
||||
/*! ./extend */
|
||||
/*! ./observer */
|
||||
|
||||
/*! ./style */
|
||||
|
||||
/*! ./frame */
|
||||
|
||||
/*! debounce */
|
||||
/*! ./peakcache */
|
||||
|
||||
/*! ./prevent-click */
|
||||
|
||||
/*! ./fetch */
|
||||
/*! ./request-animation-frame */
|
||||
|
||||
/*! ./style */
|
||||
|
||||
/*! ./util */
|
||||
|
||||
/*! ./util/get-id */
|
||||
|
||||
/*! ./util/style */
|
||||
|
||||
/*! ./webaudio */
|
||||
|
||||
/*! debounce */
|
||||
|
||||
/*! no static exports found */
|
||||
|
||||
/*!***********************!*\
|
||||
!*** ./src/drawer.js ***!
|
||||
\***********************/
|
||||
|
||||
/*!*************************!*\
|
||||
!*** ./src/util/max.js ***!
|
||||
|
@ -110,17 +76,29 @@
|
|||
!*** ./src/util/min.js ***!
|
||||
\*************************/
|
||||
|
||||
/*!******************************!*\
|
||||
!*** ./src/util/observer.js ***!
|
||||
\******************************/
|
||||
/*!*************************!*\
|
||||
!*** ./src/webaudio.js ***!
|
||||
\*************************/
|
||||
|
||||
/*!***********************************!*\
|
||||
!*** ./src/util/prevent-click.js ***!
|
||||
\***********************************/
|
||||
/*!**************************!*\
|
||||
!*** ./src/peakcache.js ***!
|
||||
\**************************/
|
||||
|
||||
/*!*********************************************!*\
|
||||
!*** ./src/util/request-animation-frame.js ***!
|
||||
\*********************************************/
|
||||
/*!**************************!*\
|
||||
!*** ./src/util/ajax.js ***!
|
||||
\**************************/
|
||||
|
||||
/*!***************************!*\
|
||||
!*** ./src/util/fetch.js ***!
|
||||
\***************************/
|
||||
|
||||
/*!***************************!*\
|
||||
!*** ./src/util/frame.js ***!
|
||||
\***************************/
|
||||
|
||||
/*!***************************!*\
|
||||
!*** ./src/util/index.js ***!
|
||||
\***************************/
|
||||
|
||||
/*!***************************!*\
|
||||
!*** ./src/util/style.js ***!
|
||||
|
@ -130,20 +108,42 @@
|
|||
!*** ./src/wavesurfer.js ***!
|
||||
\***************************/
|
||||
|
||||
/*! ./drawer.multicanvas */
|
||||
/*!****************************!*\
|
||||
!*** ./src/util/extend.js ***!
|
||||
\****************************/
|
||||
|
||||
/*! ./peakcache */
|
||||
/*!****************************!*\
|
||||
!*** ./src/util/get-id.js ***!
|
||||
\****************************/
|
||||
|
||||
/*! ./mediaelement-webaudio */
|
||||
/*!*****************************!*\
|
||||
!*** ./src/mediaelement.js ***!
|
||||
\*****************************/
|
||||
|
||||
/*!*************************!*\
|
||||
!*** ./src/webaudio.js ***!
|
||||
\*************************/
|
||||
/*!******************************!*\
|
||||
!*** ./src/util/observer.js ***!
|
||||
\******************************/
|
||||
|
||||
/*!
|
||||
* escape-html
|
||||
* Copyright(c) 2012-2013 TJ Holowaychuk
|
||||
* Copyright(c) 2015 Andreas Lubbe
|
||||
* Copyright(c) 2015 Tiancheng "Timothy" Gu
|
||||
* MIT Licensed
|
||||
*/
|
||||
/*!***********************************!*\
|
||||
!*** ./src/drawer.canvasentry.js ***!
|
||||
\***********************************/
|
||||
|
||||
/*!***********************************!*\
|
||||
!*** ./src/drawer.multicanvas.js ***!
|
||||
\***********************************/
|
||||
|
||||
/*!***********************************!*\
|
||||
!*** ./src/util/prevent-click.js ***!
|
||||
\***********************************/
|
||||
|
||||
/*!**************************************!*\
|
||||
!*** ./src/mediaelement-webaudio.js ***!
|
||||
\**************************************/
|
||||
|
||||
/*!****************************************!*\
|
||||
!*** ./node_modules/debounce/index.js ***!
|
||||
\****************************************/
|
||||
|
||||
/*!*********************************************!*\
|
||||
!*** ./src/util/request-animation-frame.js ***!
|
||||
\*********************************************/
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue