Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into issue/1880
This commit is contained in:
commit
cdc153db31
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -16,11 +16,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
<details>
|
<details>
|
||||||
<summary>API Changes</summary>
|
<summary>API Changes</summary>
|
||||||
|
|
||||||
|
- **Breaking:** Pleroma API: The routes to update avatar, banner and background have been removed.
|
||||||
- **Breaking:** Image description length is limited now.
|
- **Breaking:** Image description length is limited now.
|
||||||
- **Breaking:** Emoji API: changed methods and renamed routes.
|
- **Breaking:** Emoji API: changed methods and renamed routes.
|
||||||
|
- MastodonAPI: Allow removal of avatar, banner and background.
|
||||||
- Streaming: Repeats of a user's posts will no longer be pushed to the user's stream.
|
- Streaming: Repeats of a user's posts will no longer be pushed to the user's stream.
|
||||||
- Mastodon API: Added `pleroma.metadata.fields_limits` to /api/v1/instance
|
- Mastodon API: Added `pleroma.metadata.fields_limits` to /api/v1/instance
|
||||||
- Mastodon API: On deletion, returns the original post text.
|
- Mastodon API: On deletion, returns the original post text.
|
||||||
|
- Mastodon API: Add `pleroma.unread_count` to the Marker entity.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
@ -59,8 +62,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mastodon API: Extended `/api/v1/instance`.
|
- Mastodon API: Extended `/api/v1/instance`.
|
||||||
- Mastodon API: Support for `include_types` in `/api/v1/notifications`.
|
- Mastodon API: Support for `include_types` in `/api/v1/notifications`.
|
||||||
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
|
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
|
||||||
- Mastodon API: Add support for filtering replies in public and home timelines
|
- Mastodon API: Add support for filtering replies in public and home timelines.
|
||||||
- Mastodon API: Support for `bot` field in `/api/v1/accounts/update_credentials`
|
- Mastodon API: Support for `bot` field in `/api/v1/accounts/update_credentials`.
|
||||||
|
- Mastodon API: Support irreversible property for filters.
|
||||||
- Admin API: endpoints for create/update/delete OAuth Apps.
|
- Admin API: endpoints for create/update/delete OAuth Apps.
|
||||||
- Admin API: endpoint for status view.
|
- Admin API: endpoint for status view.
|
||||||
- OTP: Add command to reload emoji packs
|
- OTP: Add command to reload emoji packs
|
||||||
|
@ -74,6 +78,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Resolving Peertube accounts with Webfinger
|
- Resolving Peertube accounts with Webfinger
|
||||||
- `blob:` urls not being allowed by connect-src CSP
|
- `blob:` urls not being allowed by connect-src CSP
|
||||||
- Mastodon API: fix `GET /api/v1/notifications` not returning the full result set
|
- Mastodon API: fix `GET /api/v1/notifications` not returning the full result set
|
||||||
|
- Rich Media Previews for Twitter links
|
||||||
|
|
||||||
## [Unreleased (patch)]
|
## [Unreleased (patch)]
|
||||||
|
|
||||||
|
@ -215,7 +220,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mastodon API: `pleroma.thread_muted` to the Status entity
|
- Mastodon API: `pleroma.thread_muted` to the Status entity
|
||||||
- Mastodon API: Mark the direct conversation as read for the author when they send a new direct message
|
- Mastodon API: Mark the direct conversation as read for the author when they send a new direct message
|
||||||
- Mastodon API, streaming: Add `pleroma.direct_conversation_id` to the `conversation` stream event payload.
|
- Mastodon API, streaming: Add `pleroma.direct_conversation_id` to the `conversation` stream event payload.
|
||||||
- Mastodon API: Add `pleroma.unread_count` to the Marker entity
|
|
||||||
- Admin API: Render whole status in grouped reports
|
- Admin API: Render whole status in grouped reports
|
||||||
- Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise).
|
- Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise).
|
||||||
- Mastodon API: Favoriting / Repeating a post multiple times will now return the identical response every time. Before, executing that action twice would return an error ("already favorited") on the second try.
|
- Mastodon API: Favoriting / Repeating a post multiple times will now return the identical response every time. Before, executing that action twice would return an error ("already favorited") on the second try.
|
||||||
|
|
|
@ -24,6 +24,7 @@ defmodule Pleroma.LoadTesting.Activities do
|
||||||
@visibility ~w(public private direct unlisted)
|
@visibility ~w(public private direct unlisted)
|
||||||
@types [
|
@types [
|
||||||
:simple,
|
:simple,
|
||||||
|
:simple_filtered,
|
||||||
:emoji,
|
:emoji,
|
||||||
:mentions,
|
:mentions,
|
||||||
:hell_thread,
|
:hell_thread,
|
||||||
|
@ -242,6 +243,15 @@ defp insert_activity(:simple, visibility, group, users, _opts) do
|
||||||
insert_local_activity(visibility, group, users, "Simple status")
|
insert_local_activity(visibility, group, users, "Simple status")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp insert_activity(:simple_filtered, visibility, group, users, _opts)
|
||||||
|
when group in @remote_groups do
|
||||||
|
insert_remote_activity(visibility, group, users, "Remote status which must be filtered")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp insert_activity(:simple_filtered, visibility, group, users, _opts) do
|
||||||
|
insert_local_activity(visibility, group, users, "Simple status which must be filtered")
|
||||||
|
end
|
||||||
|
|
||||||
defp insert_activity(:emoji, visibility, group, users, _opts)
|
defp insert_activity(:emoji, visibility, group, users, _opts)
|
||||||
when group in @remote_groups do
|
when group in @remote_groups do
|
||||||
insert_remote_activity(visibility, group, users, "Remote status with emoji :firefox:")
|
insert_remote_activity(visibility, group, users, "Remote status with emoji :firefox:")
|
||||||
|
|
|
@ -32,10 +32,22 @@ defp fetch_user(user) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp create_filter(user) do
|
||||||
|
Pleroma.Filter.create(%Pleroma.Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
phrase: "must be filtered",
|
||||||
|
hide: true
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp delete_filter(filter), do: Repo.delete(filter)
|
||||||
|
|
||||||
defp fetch_timelines(user) do
|
defp fetch_timelines(user) do
|
||||||
fetch_home_timeline(user)
|
fetch_home_timeline(user)
|
||||||
|
fetch_home_timeline_with_filter(user)
|
||||||
fetch_direct_timeline(user)
|
fetch_direct_timeline(user)
|
||||||
fetch_public_timeline(user)
|
fetch_public_timeline(user)
|
||||||
|
fetch_public_timeline_with_filter(user)
|
||||||
fetch_public_timeline(user, :with_blocks)
|
fetch_public_timeline(user, :with_blocks)
|
||||||
fetch_public_timeline(user, :local)
|
fetch_public_timeline(user, :local)
|
||||||
fetch_public_timeline(user, :tag)
|
fetch_public_timeline(user, :tag)
|
||||||
|
@ -61,7 +73,7 @@ defp opts_for_home_timeline(user) do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp fetch_home_timeline(user) do
|
defp fetch_home_timeline(user, title_end \\ "") do
|
||||||
opts = opts_for_home_timeline(user)
|
opts = opts_for_home_timeline(user)
|
||||||
|
|
||||||
recipients = [user.ap_id | User.following(user)]
|
recipients = [user.ap_id | User.following(user)]
|
||||||
|
@ -84,9 +96,11 @@ defp fetch_home_timeline(user) do
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|> List.last()
|
|> List.last()
|
||||||
|
|
||||||
|
title = "home timeline " <> title_end
|
||||||
|
|
||||||
Benchee.run(
|
Benchee.run(
|
||||||
%{
|
%{
|
||||||
"home timeline" => fn opts -> ActivityPub.fetch_activities(recipients, opts) end
|
title => fn opts -> ActivityPub.fetch_activities(recipients, opts) end
|
||||||
},
|
},
|
||||||
inputs: %{
|
inputs: %{
|
||||||
"1 page" => opts,
|
"1 page" => opts,
|
||||||
|
@ -108,6 +122,14 @@ defp fetch_home_timeline(user) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp fetch_home_timeline_with_filter(user) do
|
||||||
|
{:ok, filter} = create_filter(user)
|
||||||
|
|
||||||
|
fetch_home_timeline(user, "with filters")
|
||||||
|
|
||||||
|
delete_filter(filter)
|
||||||
|
end
|
||||||
|
|
||||||
defp opts_for_direct_timeline(user) do
|
defp opts_for_direct_timeline(user) do
|
||||||
%{
|
%{
|
||||||
visibility: "direct",
|
visibility: "direct",
|
||||||
|
@ -210,6 +232,14 @@ defp fetch_public_timeline(user) do
|
||||||
fetch_public_timeline(opts, "public timeline")
|
fetch_public_timeline(opts, "public timeline")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp fetch_public_timeline_with_filter(user) do
|
||||||
|
{:ok, filter} = create_filter(user)
|
||||||
|
opts = opts_for_public_timeline(user)
|
||||||
|
|
||||||
|
fetch_public_timeline(opts, "public timeline with filters")
|
||||||
|
delete_filter(filter)
|
||||||
|
end
|
||||||
|
|
||||||
defp fetch_public_timeline(user, :local) do
|
defp fetch_public_timeline(user, :local) do
|
||||||
opts = opts_for_public_timeline(user, :local)
|
opts = opts_for_public_timeline(user, :local)
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,7 @@
|
||||||
"dat",
|
"dat",
|
||||||
"dweb",
|
"dweb",
|
||||||
"gopher",
|
"gopher",
|
||||||
|
"hyper",
|
||||||
"ipfs",
|
"ipfs",
|
||||||
"ipns",
|
"ipns",
|
||||||
"irc",
|
"irc",
|
||||||
|
@ -437,8 +438,7 @@
|
||||||
|
|
||||||
config :pleroma, Pleroma.Web.Preload,
|
config :pleroma, Pleroma.Web.Preload,
|
||||||
providers: [
|
providers: [
|
||||||
Pleroma.Web.Preload.Providers.Instance,
|
Pleroma.Web.Preload.Providers.Instance
|
||||||
Pleroma.Web.Preload.Providers.StatusNet
|
|
||||||
]
|
]
|
||||||
|
|
||||||
config :pleroma, :http_security,
|
config :pleroma, :http_security,
|
||||||
|
|
|
@ -498,6 +498,7 @@
|
||||||
"dat",
|
"dat",
|
||||||
"dweb",
|
"dweb",
|
||||||
"gopher",
|
"gopher",
|
||||||
|
"hyper",
|
||||||
"ipfs",
|
"ipfs",
|
||||||
"ipns",
|
"ipns",
|
||||||
"irc",
|
"irc",
|
||||||
|
@ -699,8 +700,9 @@
|
||||||
key: :public,
|
key: :public,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description:
|
description:
|
||||||
"Makes the client API in authentificated mode-only except for user-profiles." <>
|
"Makes the client API in authenticated mode-only except for user-profiles." <>
|
||||||
" Useful for disabling the Local Timeline and The Whole Known Network."
|
" Useful for disabling the Local Timeline and The Whole Known Network. " <>
|
||||||
|
" Note: when setting to `false`, please also check `:restrict_unauthenticated` setting."
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :quarantined_instances,
|
key: :quarantined_instances,
|
||||||
|
|
|
@ -182,10 +182,12 @@ Additional parameters can be added to the JSON body/Form data:
|
||||||
- `pleroma_settings_store` - Opaque user settings to be saved on the backend.
|
- `pleroma_settings_store` - Opaque user settings to be saved on the backend.
|
||||||
- `skip_thread_containment` - if true, skip filtering out broken threads
|
- `skip_thread_containment` - if true, skip filtering out broken threads
|
||||||
- `allow_following_move` - if true, allows automatically follow moved following accounts
|
- `allow_following_move` - if true, allows automatically follow moved following accounts
|
||||||
- `pleroma_background_image` - sets the background image of the user.
|
- `pleroma_background_image` - sets the background image of the user. Can be set to "" (an empty string) to reset.
|
||||||
- `discoverable` - if true, discovery of this account in search results and other services is allowed.
|
- `discoverable` - if true, discovery of this account in search results and other services is allowed.
|
||||||
- `actor_type` - the type of this account.
|
- `actor_type` - the type of this account.
|
||||||
|
|
||||||
|
All images (avatar, banner and background) can be reset to the default by sending an empty string ("") instead of a file.
|
||||||
|
|
||||||
### Pleroma Settings Store
|
### Pleroma Settings Store
|
||||||
|
|
||||||
Pleroma has mechanism that allows frontends to save blobs of json for each user on the backend. This can be used to save frontend-specific settings for a user that the backend does not need to know about.
|
Pleroma has mechanism that allows frontends to save blobs of json for each user on the backend. This can be used to save frontend-specific settings for a user that the backend does not need to know about.
|
||||||
|
|
|
@ -37,7 +37,7 @@ To add configuration to your config file, you can copy it from the base config.
|
||||||
* `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.
|
* `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.
|
||||||
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
|
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
|
||||||
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance.
|
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance.
|
||||||
* `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
|
* `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. See also: `restrict_unauthenticated`.
|
||||||
* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
|
* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
|
||||||
* `managed_config`: Whenether the config for pleroma-fe is configured in [:frontend_configurations](#frontend_configurations) or in ``static/config.json``.
|
* `managed_config`: Whenether the config for pleroma-fe is configured in [:frontend_configurations](#frontend_configurations) or in ``static/config.json``.
|
||||||
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML).
|
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML).
|
||||||
|
@ -971,11 +971,11 @@ config :pleroma, :database_config_whitelist, [
|
||||||
|
|
||||||
### :restrict_unauthenticated
|
### :restrict_unauthenticated
|
||||||
|
|
||||||
Restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
|
Restrict access for unauthenticated users to timelines (public and federated), user profiles and statuses.
|
||||||
|
|
||||||
* `timelines`: public and federated timelines
|
* `timelines`: public and federated timelines
|
||||||
* `local`: public timeline
|
* `local`: public timeline
|
||||||
* `federated`
|
* `federated`: federated timeline (includes public timeline)
|
||||||
* `profiles`: user profiles
|
* `profiles`: user profiles
|
||||||
* `local`
|
* `local`
|
||||||
* `remote`
|
* `remote`
|
||||||
|
@ -983,6 +983,7 @@ Restrict access for unauthenticated users to timelines (public and federate), us
|
||||||
* `local`
|
* `local`
|
||||||
* `remote`
|
* `remote`
|
||||||
|
|
||||||
|
Note: setting `restrict_unauthenticated/timelines/local` to `true` has no practical sense if `restrict_unauthenticated/timelines/federated` is set to `false` (since local public activities will still be delivered to unauthenticated users as part of federated timeline).
|
||||||
|
|
||||||
## Pleroma.Web.ApiSpec.CastAndValidate
|
## Pleroma.Web.ApiSpec.CastAndValidate
|
||||||
|
|
||||||
|
|
|
@ -34,10 +34,18 @@ def get(id, %{id: user_id} = _user) do
|
||||||
Repo.one(query)
|
Repo.one(query)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_filters(%User{id: user_id} = _user) do
|
def get_active(query) do
|
||||||
|
from(f in query, where: is_nil(f.expires_at) or f.expires_at > ^NaiveDateTime.utc_now())
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_irreversible(query) do
|
||||||
|
from(f in query, where: f.hide)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_filters(query \\ __MODULE__, %User{id: user_id}) do
|
||||||
query =
|
query =
|
||||||
from(
|
from(
|
||||||
f in Pleroma.Filter,
|
f in query,
|
||||||
where: f.user_id == ^user_id,
|
where: f.user_id == ^user_id,
|
||||||
order_by: [desc: :id]
|
order_by: [desc: :id]
|
||||||
)
|
)
|
||||||
|
@ -95,4 +103,34 @@ def update(%Pleroma.Filter{} = filter, params) do
|
||||||
|> validate_required([:phrase, :context])
|
|> validate_required([:phrase, :context])
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def compose_regex(user_or_filters, format \\ :postgres)
|
||||||
|
|
||||||
|
def compose_regex(%User{} = user, format) do
|
||||||
|
__MODULE__
|
||||||
|
|> get_active()
|
||||||
|
|> get_irreversible()
|
||||||
|
|> get_filters(user)
|
||||||
|
|> compose_regex(format)
|
||||||
|
end
|
||||||
|
|
||||||
|
def compose_regex([_ | _] = filters, format) do
|
||||||
|
phrases =
|
||||||
|
filters
|
||||||
|
|> Enum.map(& &1.phrase)
|
||||||
|
|> Enum.join("|")
|
||||||
|
|
||||||
|
case format do
|
||||||
|
:postgres ->
|
||||||
|
"\\y(#{phrases})\\y"
|
||||||
|
|
||||||
|
:re ->
|
||||||
|
~r/\b#{phrases}\b/i
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def compose_regex(_, _), do: nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -130,6 +130,7 @@ def for_user_query(user, opts \\ %{}) do
|
||||||
|> preload([n, a, o], activity: {a, object: o})
|
|> preload([n, a, o], activity: {a, object: o})
|
||||||
|> exclude_notification_muted(user, exclude_notification_muted_opts)
|
|> exclude_notification_muted(user, exclude_notification_muted_opts)
|
||||||
|> exclude_blocked(user, exclude_blocked_opts)
|
|> exclude_blocked(user, exclude_blocked_opts)
|
||||||
|
|> exclude_filtered(user)
|
||||||
|> exclude_visibility(opts)
|
|> exclude_visibility(opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -158,6 +159,20 @@ defp exclude_notification_muted(query, user, opts) do
|
||||||
|> where([n, a, o, tm], is_nil(tm.user_id))
|
|> where([n, a, o, tm], is_nil(tm.user_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp exclude_filtered(query, user) do
|
||||||
|
case Pleroma.Filter.compose_regex(user) do
|
||||||
|
nil ->
|
||||||
|
query
|
||||||
|
|
||||||
|
regex ->
|
||||||
|
from([_n, a, o] in query,
|
||||||
|
where:
|
||||||
|
fragment("not(?->>'content' ~* ?)", o.data, ^regex) or
|
||||||
|
fragment("?->>'actor' = ?", o.data, ^user.ap_id)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@valid_visibilities ~w[direct unlisted public private]
|
@valid_visibilities ~w[direct unlisted public private]
|
||||||
|
|
||||||
defp exclude_visibility(query, %{exclude_visibilities: visibility})
|
defp exclude_visibility(query, %{exclude_visibilities: visibility})
|
||||||
|
@ -337,6 +352,7 @@ def dismiss(%{id: user_id} = _user, id) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec create_notifications(Activity.t(), keyword()) :: {:ok, [Notification.t()] | []}
|
||||||
def create_notifications(activity, options \\ [])
|
def create_notifications(activity, options \\ [])
|
||||||
|
|
||||||
def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity, options) do
|
def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity, options) do
|
||||||
|
@ -555,7 +571,8 @@ def skip?(%Activity{} = activity, %User{} = user) do
|
||||||
:follows,
|
:follows,
|
||||||
:non_followers,
|
:non_followers,
|
||||||
:non_follows,
|
:non_follows,
|
||||||
:recently_followed
|
:recently_followed,
|
||||||
|
:filtered
|
||||||
]
|
]
|
||||||
|> Enum.find(&skip?(&1, activity, user))
|
|> Enum.find(&skip?(&1, activity, user))
|
||||||
end
|
end
|
||||||
|
@ -624,6 +641,26 @@ def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity,
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def skip?(:filtered, %{data: %{"type" => type}}, _) when type in ["Follow", "Move"], do: false
|
||||||
|
|
||||||
|
def skip?(:filtered, activity, user) do
|
||||||
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
|
cond do
|
||||||
|
is_nil(object) ->
|
||||||
|
false
|
||||||
|
|
||||||
|
object.data["actor"] == user.ap_id ->
|
||||||
|
false
|
||||||
|
|
||||||
|
not is_nil(regex = Pleroma.Filter.compose_regex(user, :re)) ->
|
||||||
|
Regex.match?(regex, object.data["content"])
|
||||||
|
|
||||||
|
true ->
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def skip?(_, _, _), do: false
|
def skip?(_, _, _), do: false
|
||||||
|
|
||||||
def for_user_and_activity(user, activity) do
|
def for_user_and_activity(user, activity) do
|
||||||
|
|
|
@ -89,7 +89,7 @@ defmodule Pleroma.User do
|
||||||
field(:keys, :string)
|
field(:keys, :string)
|
||||||
field(:public_key, :string)
|
field(:public_key, :string)
|
||||||
field(:ap_id, :string)
|
field(:ap_id, :string)
|
||||||
field(:avatar, :map)
|
field(:avatar, :map, default: %{})
|
||||||
field(:local, :boolean, default: true)
|
field(:local, :boolean, default: true)
|
||||||
field(:follower_address, :string)
|
field(:follower_address, :string)
|
||||||
field(:following_address, :string)
|
field(:following_address, :string)
|
||||||
|
@ -539,15 +539,12 @@ defp put_emoji(changeset) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_change_if_present(changeset, map_field, value_function) do
|
defp put_change_if_present(changeset, map_field, value_function) do
|
||||||
if value = get_change(changeset, map_field) do
|
with {:ok, value} <- fetch_change(changeset, map_field),
|
||||||
with {:ok, new_value} <- value_function.(value) do
|
{:ok, new_value} <- value_function.(value) do
|
||||||
put_change(changeset, map_field, new_value)
|
put_change(changeset, map_field, new_value)
|
||||||
else
|
else
|
||||||
_ -> changeset
|
_ -> changeset
|
||||||
end
|
end
|
||||||
else
|
|
||||||
changeset
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_upload(value, type) do
|
defp put_upload(value, type) do
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
alias Pleroma.Constants
|
alias Pleroma.Constants
|
||||||
alias Pleroma.Conversation
|
alias Pleroma.Conversation
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
|
alias Pleroma.Filter
|
||||||
alias Pleroma.Maps
|
alias Pleroma.Maps
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
@ -446,6 +447,7 @@ def fetch_activities_for_context_query(context, opts) do
|
||||||
|> maybe_set_thread_muted_field(opts)
|
|> maybe_set_thread_muted_field(opts)
|
||||||
|> restrict_blocked(opts)
|
|> restrict_blocked(opts)
|
||||||
|> restrict_recipients(recipients, opts[:user])
|
|> restrict_recipients(recipients, opts[:user])
|
||||||
|
|> restrict_filtered(opts)
|
||||||
|> where(
|
|> where(
|
||||||
[activity],
|
[activity],
|
||||||
fragment(
|
fragment(
|
||||||
|
@ -961,6 +963,26 @@ defp restrict_instance(query, %{instance: instance}) do
|
||||||
|
|
||||||
defp restrict_instance(query, _), do: query
|
defp restrict_instance(query, _), do: query
|
||||||
|
|
||||||
|
defp restrict_filtered(query, %{user: %User{} = user}) do
|
||||||
|
case Filter.compose_regex(user) do
|
||||||
|
nil ->
|
||||||
|
query
|
||||||
|
|
||||||
|
regex ->
|
||||||
|
from([activity, object] in query,
|
||||||
|
where:
|
||||||
|
fragment("not(?->>'content' ~* ?)", object.data, ^regex) or
|
||||||
|
activity.actor == ^user.ap_id
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_filtered(query, %{blocking_user: %User{} = user}) do
|
||||||
|
restrict_filtered(query, %{user: user})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_filtered(query, _), do: query
|
||||||
|
|
||||||
defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query
|
defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query
|
||||||
|
|
||||||
defp exclude_poll_votes(query, _) do
|
defp exclude_poll_votes(query, _) do
|
||||||
|
@ -1091,6 +1113,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
||||||
|> restrict_favorited_by(opts)
|
|> restrict_favorited_by(opts)
|
||||||
|> restrict_blocked(restrict_blocked_opts)
|
|> restrict_blocked(restrict_blocked_opts)
|
||||||
|> restrict_muted(restrict_muted_opts)
|
|> restrict_muted(restrict_muted_opts)
|
||||||
|
|> restrict_filtered(opts)
|
||||||
|> restrict_media(opts)
|
|> restrict_media(opts)
|
||||||
|> restrict_visibility(opts)
|
|> restrict_visibility(opts)
|
||||||
|> restrict_thread_visibility(opts, config)
|
|> restrict_thread_visibility(opts, config)
|
||||||
|
@ -1099,6 +1122,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
||||||
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|
||||||
|> restrict_instance(opts)
|
|> restrict_instance(opts)
|
||||||
|> restrict_announce_object_actor(opts)
|
|> restrict_announce_object_actor(opts)
|
||||||
|
|> restrict_filtered(opts)
|
||||||
|> Activity.restrict_deactivated_users()
|
|> Activity.restrict_deactivated_users()
|
||||||
|> exclude_poll_votes(opts)
|
|> exclude_poll_votes(opts)
|
||||||
|> exclude_chat_messages(opts)
|
|> exclude_chat_messages(opts)
|
||||||
|
|
|
@ -233,8 +233,10 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
|
||||||
is_map(url) && is_binary(url["href"]) -> url["href"]
|
is_map(url) && is_binary(url["href"]) -> url["href"]
|
||||||
is_binary(data["url"]) -> data["url"]
|
is_binary(data["url"]) -> data["url"]
|
||||||
is_binary(data["href"]) -> data["href"]
|
is_binary(data["href"]) -> data["href"]
|
||||||
|
true -> nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if href do
|
||||||
attachment_url =
|
attachment_url =
|
||||||
%{"href" => href}
|
%{"href" => href}
|
||||||
|> Maps.put_if_present("mediaType", media_type)
|
|> Maps.put_if_present("mediaType", media_type)
|
||||||
|
@ -244,7 +246,11 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
|
||||||
|> Maps.put_if_present("mediaType", media_type)
|
|> Maps.put_if_present("mediaType", media_type)
|
||||||
|> Maps.put_if_present("type", data["type"])
|
|> Maps.put_if_present("type", data["type"])
|
||||||
|> Maps.put_if_present("name", data["name"])
|
|> Maps.put_if_present("name", data["name"])
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
|
||||||
Map.put(object, "attachment", attachments)
|
Map.put(object, "attachment", attachments)
|
||||||
end
|
end
|
||||||
|
@ -263,12 +269,18 @@ def fix_url(%{"url" => url} = object) when is_map(url) do
|
||||||
|
|
||||||
def fix_url(%{"type" => object_type, "url" => url} = object)
|
def fix_url(%{"type" => object_type, "url" => url} = object)
|
||||||
when object_type in ["Video", "Audio"] and is_list(url) do
|
when object_type in ["Video", "Audio"] and is_list(url) do
|
||||||
first_element = Enum.at(url, 0)
|
attachment =
|
||||||
|
Enum.find(url, fn x ->
|
||||||
|
media_type = x["mediaType"] || x["mimeType"] || ""
|
||||||
|
|
||||||
link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
|
is_map(x) and String.starts_with?(media_type, ["audio/", "video/"])
|
||||||
|
end)
|
||||||
|
|
||||||
|
link_element =
|
||||||
|
Enum.find(url, fn x -> is_map(x) and (x["mediaType"] || x["mimeType"]) == "text/html" end)
|
||||||
|
|
||||||
object
|
object
|
||||||
|> Map.put("attachment", [first_element])
|
|> Map.put("attachment", [attachment])
|
||||||
|> Map.put("url", link_element["href"])
|
|> Map.put("url", link_element["href"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
|
defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
|
||||||
alias OpenApiSpex.Operation
|
alias OpenApiSpex.Operation
|
||||||
alias OpenApiSpex.Schema
|
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
|
alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||||
|
@ -40,48 +39,6 @@ def confirmation_resend_operation do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_avatar_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Accounts"],
|
|
||||||
summary: "Set/clear user avatar image",
|
|
||||||
operationId: "PleromaAPI.AccountController.update_avatar",
|
|
||||||
requestBody:
|
|
||||||
request_body("Parameters", update_avatar_or_background_request(), required: true),
|
|
||||||
security: [%{"oAuth" => ["write:accounts"]}],
|
|
||||||
responses: %{
|
|
||||||
200 => update_response(),
|
|
||||||
403 => Operation.response("Forbidden", "application/json", ApiError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_banner_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Accounts"],
|
|
||||||
summary: "Set/clear user banner image",
|
|
||||||
operationId: "PleromaAPI.AccountController.update_banner",
|
|
||||||
requestBody: request_body("Parameters", update_banner_request(), required: true),
|
|
||||||
security: [%{"oAuth" => ["write:accounts"]}],
|
|
||||||
responses: %{
|
|
||||||
200 => update_response()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_background_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Accounts"],
|
|
||||||
summary: "Set/clear user background image",
|
|
||||||
operationId: "PleromaAPI.AccountController.update_background",
|
|
||||||
security: [%{"oAuth" => ["write:accounts"]}],
|
|
||||||
requestBody:
|
|
||||||
request_body("Parameters", update_avatar_or_background_request(), required: true),
|
|
||||||
responses: %{
|
|
||||||
200 => update_response()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def favourites_operation do
|
def favourites_operation do
|
||||||
%Operation{
|
%Operation{
|
||||||
tags: ["Accounts"],
|
tags: ["Accounts"],
|
||||||
|
@ -136,52 +93,4 @@ defp id_param do
|
||||||
required: true
|
required: true
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp update_avatar_or_background_request do
|
|
||||||
%Schema{
|
|
||||||
title: "PleromaAccountUpdateAvatarOrBackgroundRequest",
|
|
||||||
type: :object,
|
|
||||||
properties: %{
|
|
||||||
img: %Schema{
|
|
||||||
nullable: true,
|
|
||||||
type: :string,
|
|
||||||
format: :binary,
|
|
||||||
description: "Image encoded using `multipart/form-data` or an empty string to clear"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp update_banner_request do
|
|
||||||
%Schema{
|
|
||||||
title: "PleromaAccountUpdateBannerRequest",
|
|
||||||
type: :object,
|
|
||||||
properties: %{
|
|
||||||
banner: %Schema{
|
|
||||||
type: :string,
|
|
||||||
nullable: true,
|
|
||||||
format: :binary,
|
|
||||||
description: "Image encoded using `multipart/form-data` or an empty string to clear"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp update_response do
|
|
||||||
Operation.response("PleromaAccountUpdateResponse", "application/json", %Schema{
|
|
||||||
type: :object,
|
|
||||||
properties: %{
|
|
||||||
url: %Schema{
|
|
||||||
type: :string,
|
|
||||||
format: :uri,
|
|
||||||
nullable: true,
|
|
||||||
description: "Image URL"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
example: %{
|
|
||||||
"url" =>
|
|
||||||
"https://cofe.party/media/9d0add56-bcb6-4c0f-8225-cbbd0b6dd773/13eadb6972c9ccd3f4ffa3b8196f0e0d38b4d2f27594457c52e52946c054cd9a.gif"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -148,6 +148,13 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
|
||||||
|> Enum.filter(fn {_, value} -> not is_nil(value) end)
|
|> Enum.filter(fn {_, value} -> not is_nil(value) end)
|
||||||
|> Enum.into(%{})
|
|> Enum.into(%{})
|
||||||
|
|
||||||
|
# We use an empty string as a special value to reset
|
||||||
|
# avatars, banners, backgrounds
|
||||||
|
user_image_value = fn
|
||||||
|
"" -> {:ok, nil}
|
||||||
|
value -> {:ok, value}
|
||||||
|
end
|
||||||
|
|
||||||
user_params =
|
user_params =
|
||||||
[
|
[
|
||||||
:no_rich_text,
|
:no_rich_text,
|
||||||
|
@ -168,9 +175,9 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
|
||||||
|> Maps.put_if_present(:name, params[:display_name])
|
|> Maps.put_if_present(:name, params[:display_name])
|
||||||
|> Maps.put_if_present(:bio, params[:note])
|
|> Maps.put_if_present(:bio, params[:note])
|
||||||
|> Maps.put_if_present(:raw_bio, params[:note])
|
|> Maps.put_if_present(:raw_bio, params[:note])
|
||||||
|> Maps.put_if_present(:avatar, params[:avatar])
|
|> Maps.put_if_present(:avatar, params[:avatar], user_image_value)
|
||||||
|> Maps.put_if_present(:banner, params[:header])
|
|> Maps.put_if_present(:banner, params[:header], user_image_value)
|
||||||
|> Maps.put_if_present(:background, params[:pleroma_background_image])
|
|> Maps.put_if_present(:background, params[:pleroma_background_image], user_image_value)
|
||||||
|> Maps.put_if_present(
|
|> Maps.put_if_present(
|
||||||
:raw_fields,
|
:raw_fields,
|
||||||
params[:fields_attributes],
|
params[:fields_attributes],
|
||||||
|
|
|
@ -88,21 +88,20 @@ def direct(%{assigns: %{user: user}} = conn, params) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp restrict_unauthenticated?(true = _local_only) do
|
||||||
|
Pleroma.Config.get([:restrict_unauthenticated, :timelines, :local])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_unauthenticated?(_) do
|
||||||
|
Pleroma.Config.get([:restrict_unauthenticated, :timelines, :federated])
|
||||||
|
end
|
||||||
|
|
||||||
# GET /api/v1/timelines/public
|
# GET /api/v1/timelines/public
|
||||||
def public(%{assigns: %{user: user}} = conn, params) do
|
def public(%{assigns: %{user: user}} = conn, params) do
|
||||||
local_only = params[:local]
|
local_only = params[:local]
|
||||||
|
|
||||||
cfg_key =
|
if is_nil(user) and restrict_unauthenticated?(local_only) do
|
||||||
if local_only do
|
fail_on_bad_auth(conn)
|
||||||
:local
|
|
||||||
else
|
|
||||||
:federated
|
|
||||||
end
|
|
||||||
|
|
||||||
restrict? = Pleroma.Config.get([:restrict_unauthenticated, :timelines, cfg_key])
|
|
||||||
|
|
||||||
if restrict? and is_nil(user) do
|
|
||||||
render_error(conn, :unauthorized, "authorization required for timeline view")
|
|
||||||
else
|
else
|
||||||
activities =
|
activities =
|
||||||
params
|
params
|
||||||
|
@ -123,6 +122,10 @@ def public(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp fail_on_bad_auth(conn) do
|
||||||
|
render_error(conn, :unauthorized, "authorization required for timeline view")
|
||||||
|
end
|
||||||
|
|
||||||
defp hashtag_fetching(params, user, local_only) do
|
defp hashtag_fetching(params, user, local_only) do
|
||||||
tags =
|
tags =
|
||||||
[params[:tag], params[:any]]
|
[params[:tag], params[:any]]
|
||||||
|
@ -157,6 +160,10 @@ defp hashtag_fetching(params, user, local_only) do
|
||||||
# GET /api/v1/timelines/tag/:tag
|
# GET /api/v1/timelines/tag/:tag
|
||||||
def hashtag(%{assigns: %{user: user}} = conn, params) do
|
def hashtag(%{assigns: %{user: user}} = conn, params) do
|
||||||
local_only = params[:local]
|
local_only = params[:local]
|
||||||
|
|
||||||
|
if is_nil(user) and restrict_unauthenticated?(local_only) do
|
||||||
|
fail_on_bad_auth(conn)
|
||||||
|
else
|
||||||
activities = hashtag_fetching(params, user, local_only)
|
activities = hashtag_fetching(params, user, local_only)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
@ -167,6 +174,7 @@ def hashtag(%{assigns: %{user: user}} = conn, params) do
|
||||||
as: :activity
|
as: :activity
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# GET /api/v1/timelines/list/:list_id
|
# GET /api/v1/timelines/list/:list_id
|
||||||
def list(%{assigns: %{user: user}} = conn, %{list_id: id} = params) do
|
def list(%{assigns: %{user: user}} = conn, %{list_id: id} = params) do
|
||||||
|
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
||||||
import Pleroma.Web.ControllerHelper,
|
import Pleroma.Web.ControllerHelper,
|
||||||
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
|
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
|
||||||
|
|
||||||
alias Ecto.Changeset
|
|
||||||
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
|
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
|
||||||
alias Pleroma.Plugs.OAuthScopesPlug
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
alias Pleroma.Plugs.RateLimiter
|
alias Pleroma.Plugs.RateLimiter
|
||||||
|
@ -35,17 +34,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
||||||
%{scopes: ["follow", "write:follows"]} when action in [:subscribe, :unsubscribe]
|
%{scopes: ["follow", "write:follows"]} when action in [:subscribe, :unsubscribe]
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(
|
|
||||||
OAuthScopesPlug,
|
|
||||||
%{scopes: ["write:accounts"]}
|
|
||||||
# Note: the following actions are not permission-secured in Mastodon:
|
|
||||||
when action in [
|
|
||||||
:update_avatar,
|
|
||||||
:update_banner,
|
|
||||||
:update_background
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites
|
%{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites
|
||||||
|
@ -68,56 +56,6 @@ def confirmation_resend(conn, params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "PATCH /api/v1/pleroma/accounts/update_avatar"
|
|
||||||
def update_avatar(%{assigns: %{user: user}, body_params: %{img: ""}} = conn, _) do
|
|
||||||
{:ok, _user} =
|
|
||||||
user
|
|
||||||
|> Changeset.change(%{avatar: nil})
|
|
||||||
|> User.update_and_set_cache()
|
|
||||||
|
|
||||||
json(conn, %{url: nil})
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_avatar(%{assigns: %{user: user}, body_params: params} = conn, _params) do
|
|
||||||
{:ok, %{data: data}} = ActivityPub.upload(params, type: :avatar)
|
|
||||||
{:ok, _user} = user |> Changeset.change(%{avatar: data}) |> User.update_and_set_cache()
|
|
||||||
%{"url" => [%{"href" => href} | _]} = data
|
|
||||||
|
|
||||||
json(conn, %{url: href})
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc "PATCH /api/v1/pleroma/accounts/update_banner"
|
|
||||||
def update_banner(%{assigns: %{user: user}, body_params: %{banner: ""}} = conn, _) do
|
|
||||||
with {:ok, _user} <- User.update_banner(user, %{}) do
|
|
||||||
json(conn, %{url: nil})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_banner(%{assigns: %{user: user}, body_params: params} = conn, _) do
|
|
||||||
with {:ok, object} <- ActivityPub.upload(%{img: params[:banner]}, type: :banner),
|
|
||||||
{:ok, _user} <- User.update_banner(user, object.data) do
|
|
||||||
%{"url" => [%{"href" => href} | _]} = object.data
|
|
||||||
|
|
||||||
json(conn, %{url: href})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc "PATCH /api/v1/pleroma/accounts/update_background"
|
|
||||||
def update_background(%{assigns: %{user: user}, body_params: %{img: ""}} = conn, _) do
|
|
||||||
with {:ok, _user} <- User.update_background(user, %{}) do
|
|
||||||
json(conn, %{url: nil})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_background(%{assigns: %{user: user}, body_params: params} = conn, _) do
|
|
||||||
with {:ok, object} <- ActivityPub.upload(params, type: :background),
|
|
||||||
{:ok, _user} <- User.update_background(user, object.data) do
|
|
||||||
%{"url" => [%{"href" => href} | _]} = object.data
|
|
||||||
|
|
||||||
json(conn, %{url: href})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc "GET /api/v1/pleroma/accounts/:id/favourites"
|
@doc "GET /api/v1/pleroma/accounts/:id/favourites"
|
||||||
def favourites(%{assigns: %{account: %{hide_favorites: true}}} = conn, _params) do
|
def favourites(%{assigns: %{account: %{hide_favorites: true}}} = conn, _params) do
|
||||||
render_error(conn, :forbidden, "Can't get favorites")
|
render_error(conn, :forbidden, "Can't get favorites")
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.Preload.Providers.StatusNet do
|
|
||||||
alias Pleroma.Web.Preload.Providers.Provider
|
|
||||||
alias Pleroma.Web.TwitterAPI.UtilController
|
|
||||||
|
|
||||||
@behaviour Provider
|
|
||||||
@config_url "/api/statusnet/config.json"
|
|
||||||
|
|
||||||
@impl Provider
|
|
||||||
def generate_terms(_params) do
|
|
||||||
%{}
|
|
||||||
|> build_config_tag()
|
|
||||||
end
|
|
||||||
|
|
||||||
defp build_config_tag(acc) do
|
|
||||||
resp =
|
|
||||||
Plug.Test.conn(:get, @config_url |> to_string())
|
|
||||||
|> UtilController.config(nil)
|
|
||||||
|
|
||||||
Map.put(acc, @config_url, resp.resp_body)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -86,7 +86,10 @@ defp parse_url(url) do
|
||||||
end
|
end
|
||||||
|
|
||||||
try do
|
try do
|
||||||
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: opts)
|
rich_media_agent = Pleroma.Application.user_agent() <> "; Bot"
|
||||||
|
|
||||||
|
{:ok, %Tesla.Env{body: html}} =
|
||||||
|
Pleroma.HTTP.get(url, [{"user-agent", rich_media_agent}], adapter: opts)
|
||||||
|
|
||||||
html
|
html
|
||||||
|> parse_html()
|
|> parse_html()
|
||||||
|
|
|
@ -328,10 +328,6 @@ defmodule Pleroma.Web.Router do
|
||||||
delete("/statuses/:id/reactions/:emoji", EmojiReactionController, :delete)
|
delete("/statuses/:id/reactions/:emoji", EmojiReactionController, :delete)
|
||||||
post("/notifications/read", NotificationController, :mark_as_read)
|
post("/notifications/read", NotificationController, :mark_as_read)
|
||||||
|
|
||||||
patch("/accounts/update_avatar", AccountController, :update_avatar)
|
|
||||||
patch("/accounts/update_banner", AccountController, :update_banner)
|
|
||||||
patch("/accounts/update_background", AccountController, :update_background)
|
|
||||||
|
|
||||||
get("/mascot", MascotController, :show)
|
get("/mascot", MascotController, :show)
|
||||||
put("/mascot", MascotController, :update)
|
put("/mascot", MascotController, :update)
|
||||||
|
|
||||||
|
@ -516,10 +512,6 @@ defmodule Pleroma.Web.Router do
|
||||||
scope "/api", Pleroma.Web do
|
scope "/api", Pleroma.Web do
|
||||||
pipe_through(:config)
|
pipe_through(:config)
|
||||||
|
|
||||||
get("/help/test", TwitterAPI.UtilController, :help_test)
|
|
||||||
post("/help/test", TwitterAPI.UtilController, :help_test)
|
|
||||||
get("/statusnet/config", TwitterAPI.UtilController, :config)
|
|
||||||
get("/statusnet/version", TwitterAPI.UtilController, :version)
|
|
||||||
get("/pleroma/frontend_configurations", TwitterAPI.UtilController, :frontend_configurations)
|
get("/pleroma/frontend_configurations", TwitterAPI.UtilController, :frontend_configurations)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Plugs.OAuthScopesPlug
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web
|
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.TwitterAPI.UtilView
|
|
||||||
alias Pleroma.Web.WebFinger
|
alias Pleroma.Web.WebFinger
|
||||||
|
|
||||||
plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe)
|
plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe)
|
||||||
|
@ -42,12 +40,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||||
|
|
||||||
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
|
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
|
||||||
|
|
||||||
plug(Pleroma.Plugs.SetFormatPlug when action in [:config, :version])
|
|
||||||
|
|
||||||
def help_test(conn, _params) do
|
|
||||||
json(conn, "ok")
|
|
||||||
end
|
|
||||||
|
|
||||||
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
|
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
|
||||||
with %User{} = user <- User.get_cached_by_nickname(nick),
|
with %User{} = user <- User.get_cached_by_nickname(nick),
|
||||||
avatar = User.avatar_url(user) do
|
avatar = User.avatar_url(user) do
|
||||||
|
@ -89,60 +81,6 @@ def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def config(%{assigns: %{format: "xml"}} = conn, _params) do
|
|
||||||
instance = Pleroma.Config.get(:instance)
|
|
||||||
response = UtilView.status_net_config(instance)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_resp_content_type("application/xml")
|
|
||||||
|> send_resp(200, response)
|
|
||||||
end
|
|
||||||
|
|
||||||
def config(conn, _params) do
|
|
||||||
instance = Pleroma.Config.get(:instance)
|
|
||||||
|
|
||||||
vapid_public_key = Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
|
|
||||||
|
|
||||||
uploadlimit = %{
|
|
||||||
uploadlimit: to_string(Keyword.get(instance, :upload_limit)),
|
|
||||||
avatarlimit: to_string(Keyword.get(instance, :avatar_upload_limit)),
|
|
||||||
backgroundlimit: to_string(Keyword.get(instance, :background_upload_limit)),
|
|
||||||
bannerlimit: to_string(Keyword.get(instance, :banner_upload_limit))
|
|
||||||
}
|
|
||||||
|
|
||||||
data = %{
|
|
||||||
name: Keyword.get(instance, :name),
|
|
||||||
description: Keyword.get(instance, :description),
|
|
||||||
server: Web.base_url(),
|
|
||||||
textlimit: to_string(Keyword.get(instance, :limit)),
|
|
||||||
uploadlimit: uploadlimit,
|
|
||||||
closed: bool_to_val(Keyword.get(instance, :registrations_open), "0", "1"),
|
|
||||||
private: bool_to_val(Keyword.get(instance, :public, true), "0", "1"),
|
|
||||||
vapidPublicKey: vapid_public_key,
|
|
||||||
accountActivationRequired:
|
|
||||||
bool_to_val(Keyword.get(instance, :account_activation_required, false)),
|
|
||||||
invitesEnabled: bool_to_val(Keyword.get(instance, :invites_enabled, false)),
|
|
||||||
safeDMMentionsEnabled: bool_to_val(Pleroma.Config.get([:instance, :safe_dm_mentions]))
|
|
||||||
}
|
|
||||||
|
|
||||||
managed_config = Keyword.get(instance, :managed_config)
|
|
||||||
|
|
||||||
data =
|
|
||||||
if managed_config do
|
|
||||||
pleroma_fe = Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
|
|
||||||
Map.put(data, "pleromafe", pleroma_fe)
|
|
||||||
else
|
|
||||||
data
|
|
||||||
end
|
|
||||||
|
|
||||||
json(conn, %{site: data})
|
|
||||||
end
|
|
||||||
|
|
||||||
defp bool_to_val(true), do: "1"
|
|
||||||
defp bool_to_val(_), do: "0"
|
|
||||||
defp bool_to_val(true, val, _), do: val
|
|
||||||
defp bool_to_val(_, _, val), do: val
|
|
||||||
|
|
||||||
def frontend_configurations(conn, _params) do
|
def frontend_configurations(conn, _params) do
|
||||||
config =
|
config =
|
||||||
Pleroma.Config.get(:frontend_configurations, %{})
|
Pleroma.Config.get(:frontend_configurations, %{})
|
||||||
|
@ -151,18 +89,6 @@ def frontend_configurations(conn, _params) do
|
||||||
json(conn, config)
|
json(conn, config)
|
||||||
end
|
end
|
||||||
|
|
||||||
def version(%{assigns: %{format: "xml"}} = conn, _params) do
|
|
||||||
version = Pleroma.Application.named_version()
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_resp_content_type("application/xml")
|
|
||||||
|> send_resp(200, "<version>#{version}</version>")
|
|
||||||
end
|
|
||||||
|
|
||||||
def version(conn, _params) do
|
|
||||||
json(conn, Pleroma.Application.named_version())
|
|
||||||
end
|
|
||||||
|
|
||||||
def emoji(conn, _params) do
|
def emoji(conn, _params) do
|
||||||
emoji =
|
emoji =
|
||||||
Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc ->
|
Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc ->
|
||||||
|
|
|
@ -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.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>
|
<!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/app.493b9b5acee37ba97824.css rel=stylesheet><link href=/static/fontello.1594134783339.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.8837fb59589d1dd6acda.js></script><script type=text/javascript src=/static/js/app.53001fa190f37cf2743e.js></script></body></html>
|
|
@ -14,7 +14,6 @@
|
||||||
"logoMargin": ".1em",
|
"logoMargin": ".1em",
|
||||||
"logoMask": true,
|
"logoMask": true,
|
||||||
"minimalScopesMode": false,
|
"minimalScopesMode": false,
|
||||||
"noAttachmentLinks": false,
|
|
||||||
"nsfwCensorImage": "",
|
"nsfwCensorImage": "",
|
||||||
"postContentType": "text/plain",
|
"postContentType": "text/plain",
|
||||||
"redirectRootLogin": "/main/friends",
|
"redirectRootLogin": "/main/friends",
|
||||||
|
@ -22,6 +21,7 @@
|
||||||
"scopeCopy": true,
|
"scopeCopy": true,
|
||||||
"showFeaturesPanel": true,
|
"showFeaturesPanel": true,
|
||||||
"showInstanceSpecificPanel": false,
|
"showInstanceSpecificPanel": false,
|
||||||
|
"sidebarRight": false,
|
||||||
"subjectLineBehavior": "email",
|
"subjectLineBehavior": "email",
|
||||||
"theme": "pleroma-dark",
|
"theme": "pleroma-dark",
|
||||||
"webPushNotifications": false
|
"webPushNotifications": false
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"sources":["webpack:///./src/hocs/with_subscription/with_subscription.scss"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA,C","file":"static/css/2.0778a6a864a1307a6c41.css","sourcesContent":[".with-subscription-loading {\n padding: 10px;\n text-align: center;\n}\n.with-subscription-loading .error {\n font-size: 14px;\n}"],"sourceRoot":""}
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
@ -1 +0,0 @@
|
||||||
{"version":3,"sources":["webpack:///./src/hocs/with_load_more/with_load_more.scss","webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_subscription/with_subscription.scss"],"names":[],"mappings":"AAAA,uBAAuB,aAAa,kBAAkB,qBAAqB,sBAAsB,qCAAqC,8BAA8B,e;ACApK,cAAc,oBAAoB,aAAa,0BAA0B,sBAAsB,wBAAwB,kBAAkB,cAAc,eAAe,gCAAgC,aAAa,wCAAwC,0BAA0B,aAAa,gBAAgB,oBAAoB,oBAAoB,aAAa,kBAAkB,WAAW,kBAAkB,gBAAgB,gBAAgB,sBAAsB,uDAAuD,cAAc,WAAW,kBAAkB,cAAc,wBAAwB,yBAAyB,wCAAwC,iCAAiC,YAAY,kBAAkB,oBAAoB,aAAa,kBAAkB,cAAc,sCAAsC,WAAW,cAAc,kBAAkB,4BAA4B,6BAA6B,gBAAgB,oBAAoB,oBAAoB,mBAAmB,cAAc,8BAA8B,yBAAyB,qCAAqC,mDAAmD,UAAU,yDAAyD,UAAU,6CAA6C,uBAAuB,UAAU,cAAc,oCAAoC,0CAA0C,gBAAgB,mBAAmB,gBAAgB,qDAAqD,WAAW,kBAAkB,OAAO,QAAQ,SAAS,UAAU,wBAAwB,yBAAyB,wC;ACAtlD,2BAA2B,aAAa,kBAAkB,kCAAkC,e","file":"static/css/app.613cef07981cd95ccceb.css","sourcesContent":[".with-load-more-footer{padding:10px;text-align:center;border-top:1px solid;border-top-color:#222;border-top-color:var(--border, #222)}.with-load-more-footer .error{font-size:14px}",".tab-switcher{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.tab-switcher .contents{-ms-flex:1 0 auto;flex:1 0 auto;min-height:0px}.tab-switcher .contents .hidden{display:none}.tab-switcher .contents.scrollable-tabs{-ms-flex-preferred-size:0;flex-basis:0;overflow-y:auto}.tab-switcher .tabs{display:-ms-flexbox;display:flex;position:relative;width:100%;overflow-y:hidden;overflow-x:auto;padding-top:5px;box-sizing:border-box}.tab-switcher .tabs::after,.tab-switcher .tabs::before{display:block;content:\"\";-ms-flex:1 1 auto;flex:1 1 auto;border-bottom:1px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)}.tab-switcher .tabs .tab-wrapper{height:28px;position:relative;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto}.tab-switcher .tabs .tab-wrapper .tab{width:100%;min-width:1px;position:relative;border-bottom-left-radius:0;border-bottom-right-radius:0;padding:6px 1em;padding-bottom:99px;margin-bottom:-93px;white-space:nowrap;color:#b9b9ba;color:var(--tabText, #b9b9ba);background-color:#182230;background-color:var(--tab, #182230)}.tab-switcher .tabs .tab-wrapper .tab:not(.active){z-index:4}.tab-switcher .tabs .tab-wrapper .tab:not(.active):hover{z-index:6}.tab-switcher .tabs .tab-wrapper .tab.active{background:transparent;z-index:5;color:#b9b9ba;color:var(--tabActiveText, #b9b9ba)}.tab-switcher .tabs .tab-wrapper .tab img{max-height:26px;vertical-align:top;margin-top:-5px}.tab-switcher .tabs .tab-wrapper:not(.active)::after{content:\"\";position:absolute;left:0;right:0;bottom:0;z-index:7;border-bottom:1px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)}",".with-subscription-loading{padding:10px;text-align:center}.with-subscription-loading .error{font-size:14px}"],"sourceRoot":""}
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -80,8 +80,16 @@
|
||||||
|
|
||||||
<glyph glyph-name="user" unicode="" d="M714 76q0-60-35-104t-84-44h-476q-49 0-84 44t-35 104q0 48 5 90t17 85 33 73 52 50 76 19q73-72 174-72t175 72q42 0 75-19t52-50 33-73 18-85 4-90z m-143 495q0-88-62-151t-152-63-151 63-63 151 63 152 151 63 152-63 62-152z" horiz-adv-x="714.3" />
|
<glyph glyph-name="user" unicode="" d="M714 76q0-60-35-104t-84-44h-476q-49 0-84 44t-35 104q0 48 5 90t17 85 33 73 52 50 76 19q73-72 174-72t175 72q42 0 75-19t52-50 33-73 18-85 4-90z m-143 495q0-88-62-151t-152-63-151 63-63 151 63 152 151 63 152-63 62-152z" horiz-adv-x="714.3" />
|
||||||
|
|
||||||
|
<glyph glyph-name="download" unicode="" d="M714 107q0 15-10 25t-25 11-25-11-11-25 11-25 25-11 25 11 10 25z m143 0q0 15-10 25t-26 11-25-11-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-37t-38-16h-821q-23 0-38 16t-16 37v179q0 22 16 38t38 16h259l75-76q33-32 76-32t76 32l76 76h259q22 0 38-16t16-38z m-182 318q10-23-8-39l-250-250q-10-11-25-11t-25 11l-250 250q-17 16-8 39 10 21 33 21h143v250q0 15 11 25t25 11h143q14 0 25-11t10-25v-250h143q24 0 33-21z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="bookmark" unicode="" d="M650 786q12 0 24-5 19-8 29-23t11-35v-719q0-19-11-35t-29-23q-10-4-24-4-27 0-47 18l-246 236-246-236q-20-19-46-19-13 0-25 5-18 7-29 23t-11 35v719q0 19 11 35t29 23q12 5 25 5h585z" horiz-adv-x="714.3" />
|
||||||
|
|
||||||
<glyph glyph-name="ok" unicode="" d="M933 541q0-22-16-38l-404-404-76-76q-16-15-38-15t-38 15l-76 76-202 202q-15 16-15 38t15 38l76 76q16 16 38 16t38-16l164-165 366 367q16 16 38 16t38-16l76-76q16-15 16-38z" horiz-adv-x="1000" />
|
<glyph glyph-name="ok" unicode="" d="M933 541q0-22-16-38l-404-404-76-76q-16-15-38-15t-38 15l-76 76-202 202q-15 16-15 38t15 38l76 76q16 16 38 16t38-16l164-165 366 367q16 16 38 16t38-16l76-76q16-15 16-38z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="music" unicode="" d="M857 732v-625q0-28-19-50t-48-33-58-18-53-6-54 6-58 18-48 33-19 50 19 50 48 33 58 18 54 6q58 0 107-22v300l-429-132v-396q0-28-19-50t-48-33-58-18-53-6-54 6-58 18-48 33-19 50 19 50 48 34 58 17 54 6q58 0 107-21v539q0 17 10 32t28 20l464 142q7 3 16 3 22 0 38-16t15-38z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="doc" unicode="" d="M819 645q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
<glyph glyph-name="spin3" unicode="" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" />
|
<glyph glyph-name="spin3" unicode="" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
<glyph glyph-name="spin4" unicode="" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" />
|
<glyph glyph-name="spin4" unicode="" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" />
|
||||||
|
@ -90,6 +98,10 @@
|
||||||
|
|
||||||
<glyph glyph-name="link-ext-alt" unicode="" d="M714 339v268q0 15-10 25t-25 11h-268q-24 0-33-22-10-23 8-39l80-80-298-298q-11-11-11-26t11-25l57-57q11-10 25-10t25 10l298 298 81-80q10-11 25-11 6 0 14 3 21 10 21 33z m143 286v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
|
<glyph glyph-name="link-ext-alt" unicode="" d="M714 339v268q0 15-10 25t-25 11h-268q-24 0-33-22-10-23 8-39l80-80-298-298q-11-11-11-26t11-25l57-57q11-10 25-10t25 10l298 298 81-80q10-11 25-11 6 0 14 3 21 10 21 33z m143 286v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="bookmark-empty" unicode="" d="M643 714h-572v-693l237 227 49 47 50-47 236-227v693z m7 72q12 0 24-5 19-8 29-23t11-35v-719q0-19-11-35t-29-23q-10-4-24-4-27 0-47 18l-246 236-246-236q-20-19-46-19-13 0-25 5-18 7-29 23t-11 35v719q0 19 11 35t29 23q12 5 25 5h585z" horiz-adv-x="714.3" />
|
||||||
|
|
||||||
|
<glyph glyph-name="filter" unicode="" d="M783 692q9-22-8-39l-275-275v-414q0-23-22-33-7-3-14-3-15 0-25 11l-143 143q-10 11-10 25v271l-275 275q-18 17-8 39 9 22 33 22h714q23 0 33-22z" horiz-adv-x="785.7" />
|
||||||
|
|
||||||
<glyph glyph-name="menu" unicode="" d="M857 107v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" />
|
<glyph glyph-name="menu" unicode="" d="M857 107v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
<glyph glyph-name="mail-alt" unicode="" d="M1000 461v-443q0-37-26-63t-63-27h-822q-36 0-63 27t-26 63v443q25-27 56-49 202-137 278-192 32-24 51-37t53-27 61-13h2q28 0 61 13t53 27 51 37q95 68 278 192 32 22 56 49z m0 164q0-44-27-84t-68-69q-210-146-262-181-5-4-23-17t-30-22-29-18-32-15-28-5h-2q-12 0-27 5t-32 15-30 18-30 22-23 17q-51 35-147 101t-114 80q-35 23-65 64t-31 77q0 43 23 72t66 29h822q36 0 63-26t26-63z" horiz-adv-x="1000" />
|
<glyph glyph-name="mail-alt" unicode="" d="M1000 461v-443q0-37-26-63t-63-27h-822q-36 0-63 27t-26 63v443q25-27 56-49 202-137 278-192 32-24 51-37t53-27 61-13h2q28 0 61 13t53 27 51 37q95 68 278 192 32 22 56 49z m0 164q0-44-27-84t-68-69q-210-146-262-181-5-4-23-17t-30-22-29-18-32-15-28-5h-2q-12 0-27 5t-32 15-30 18-30 22-23 17q-51 35-147 101t-114 80q-35 23-65 64t-31 77q0 43 23 72t66 29h822q36 0 63-26t26-63z" horiz-adv-x="1000" />
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 28 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -363,6 +363,42 @@
|
||||||
"css": "ok",
|
"css": "ok",
|
||||||
"code": 59431,
|
"code": 59431,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "4109c474ff99cad28fd5a2c38af2ec6f",
|
||||||
|
"css": "filter",
|
||||||
|
"code": 61616,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "9a76bc135eac17d2c8b8ad4a5774fc87",
|
||||||
|
"css": "download",
|
||||||
|
"code": 59429,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "f04a5d24e9e659145b966739c4fde82a",
|
||||||
|
"css": "bookmark",
|
||||||
|
"code": 59430,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "2f5ef6f6b7aaebc56458ab4e865beff5",
|
||||||
|
"css": "bookmark-empty",
|
||||||
|
"code": 61591,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "9ea0a737ccc45d6c510dcbae56058849",
|
||||||
|
"css": "music",
|
||||||
|
"code": 59432,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "1b5a5d7b7e3c71437f5a26befdd045ed",
|
||||||
|
"css": "doc",
|
||||||
|
"code": 59433,
|
||||||
|
"src": "fontawesome"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
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.
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.
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.
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