Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into announce-validator

This commit is contained in:
lain 2020-05-20 14:02:40 +02:00
commit c7cdc553ff
579 changed files with 2044 additions and 1492 deletions

View file

@ -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.

View file

@ -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]}

View file

@ -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,
@ -376,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,

View file

@ -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

View file

@ -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:

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
@ -188,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}

View file

@ -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)

View file

@ -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
)
|> join(:left, [relationships: r], f in User,
as: :following,
on: f.id == r.following_id
from(u in query,
where: u.ap_id in ^to or u.id in subquery(following_query)
)
|> 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

View file

@ -393,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."
},
@ -463,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."
},
@ -486,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."
},
@ -510,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"
@ -552,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"
},
@ -568,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."
@ -678,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

View file

@ -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

View file

@ -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",

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -7,6 +7,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.AccountOperation
alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
alias Pleroma.Web.ApiSpec.Schemas.ScheduledStatus
alias Pleroma.Web.ApiSpec.Schemas.Status
@ -394,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?"
}
@ -411,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?"
},
@ -435,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"

View file

@ -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?"
}

View file

@ -43,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: %{

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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(),

View file

@ -436,27 +436,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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -325,7 +325,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 +345,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

View file

@ -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"
_ -> ""
href
|> String.trim_leading("#{base_url}/#{prefix}")
|> uploader.delete_file()
{id, href}
else
_ -> {id, nil}
end
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)
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

View file

@ -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"},

View file

@ -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"},
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
@ -57,7 +57,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"},

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -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.

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