forked from AkkomaGang/akkoma
Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop
This commit is contained in:
commit
eee32fd993
139 changed files with 1946 additions and 984 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -12,16 +12,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- NodeInfo: `pleroma_emoji_reactions` to the `features` list.
|
- NodeInfo: `pleroma_emoji_reactions` to the `features` list.
|
||||||
- Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
|
- Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
|
||||||
- New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required.
|
- New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required.
|
||||||
|
- Mix task to create trusted OAuth App.
|
||||||
<details>
|
<details>
|
||||||
<summary>API Changes</summary>
|
<summary>API Changes</summary>
|
||||||
- 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.
|
||||||
|
- Admin API: endpoints for create/update/delete OAuth Apps.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Support pagination in conversations API
|
- Support pagination in conversations API
|
||||||
|
|
||||||
## [unreleased-patch]
|
## [unreleased-patch]
|
||||||
|
### Fixed
|
||||||
|
- Logger configuration through AdminFE
|
||||||
|
- HTTP Basic Authentication permissions issue
|
||||||
|
|
||||||
|
### Added
|
||||||
|
<details>
|
||||||
|
<summary>API Changes</summary>
|
||||||
|
- Admin API: `GET /api/pleroma/admin/need_reboot`.
|
||||||
|
</details>
|
||||||
|
|
||||||
## [2.0.2] - 2020-04-08
|
## [2.0.2] - 2020-04-08
|
||||||
### Added
|
### Added
|
||||||
|
@ -67,7 +78,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## [2.0.0] - 2019-03-08
|
## [2.0.0] - 2019-03-08
|
||||||
### Security
|
### Security
|
||||||
- Mastodon API: Fix being able to request enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request.
|
- Mastodon API: Fix being able to request enormous amount of statuses in timelines leading to DoS. Now limited to 40 per request.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media`
|
- **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media`
|
||||||
|
|
|
@ -386,47 +386,56 @@ defp render_timelines(user) do
|
||||||
|
|
||||||
favourites = ActivityPub.fetch_favourites(user)
|
favourites = ActivityPub.fetch_favourites(user)
|
||||||
|
|
||||||
|
output_relationships =
|
||||||
|
!!Pleroma.Config.get([:extensions, :output_relationships_in_statuses_by_default])
|
||||||
|
|
||||||
Benchee.run(
|
Benchee.run(
|
||||||
%{
|
%{
|
||||||
"Rendering home timeline" => fn ->
|
"Rendering home timeline" => fn ->
|
||||||
StatusView.render("index.json", %{
|
StatusView.render("index.json", %{
|
||||||
activities: home_activities,
|
activities: home_activities,
|
||||||
for: user,
|
for: user,
|
||||||
as: :activity
|
as: :activity,
|
||||||
|
skip_relationships: !output_relationships
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
"Rendering direct timeline" => fn ->
|
"Rendering direct timeline" => fn ->
|
||||||
StatusView.render("index.json", %{
|
StatusView.render("index.json", %{
|
||||||
activities: direct_activities,
|
activities: direct_activities,
|
||||||
for: user,
|
for: user,
|
||||||
as: :activity
|
as: :activity,
|
||||||
|
skip_relationships: !output_relationships
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
"Rendering public timeline" => fn ->
|
"Rendering public timeline" => fn ->
|
||||||
StatusView.render("index.json", %{
|
StatusView.render("index.json", %{
|
||||||
activities: public_activities,
|
activities: public_activities,
|
||||||
for: user,
|
for: user,
|
||||||
as: :activity
|
as: :activity,
|
||||||
|
skip_relationships: !output_relationships
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
"Rendering tag timeline" => fn ->
|
"Rendering tag timeline" => fn ->
|
||||||
StatusView.render("index.json", %{
|
StatusView.render("index.json", %{
|
||||||
activities: tag_activities,
|
activities: tag_activities,
|
||||||
for: user,
|
for: user,
|
||||||
as: :activity
|
as: :activity,
|
||||||
|
skip_relationships: !output_relationships
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
"Rendering notifications" => fn ->
|
"Rendering notifications" => fn ->
|
||||||
Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{
|
Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{
|
||||||
notifications: notifications,
|
notifications: notifications,
|
||||||
for: user
|
for: user,
|
||||||
|
skip_relationships: !output_relationships
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
"Rendering favourites timeline" => fn ->
|
"Rendering favourites timeline" => fn ->
|
||||||
StatusView.render("index.json", %{
|
StatusView.render("index.json", %{
|
||||||
activities: favourites,
|
activities: favourites,
|
||||||
for: user,
|
for: user,
|
||||||
as: :activity
|
as: :activity,
|
||||||
|
skip_relationships: !output_relationships
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
|
|
|
@ -241,6 +241,8 @@
|
||||||
extended_nickname_format: true,
|
extended_nickname_format: true,
|
||||||
cleanup_attachments: false
|
cleanup_attachments: false
|
||||||
|
|
||||||
|
config :pleroma, :extensions, output_relationships_in_statuses_by_default: true
|
||||||
|
|
||||||
config :pleroma, :feed,
|
config :pleroma, :feed,
|
||||||
post_title: %{
|
post_title: %{
|
||||||
max_length: 100,
|
max_length: 100,
|
||||||
|
|
|
@ -786,6 +786,8 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
|
|
||||||
### Restarts pleroma application
|
### Restarts pleroma application
|
||||||
|
|
||||||
|
**Only works when configuration from database is enabled.**
|
||||||
|
|
||||||
- Params: none
|
- Params: none
|
||||||
- Response:
|
- Response:
|
||||||
- On failure:
|
- On failure:
|
||||||
|
@ -795,11 +797,24 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
{}
|
{}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `GET /api/pleroma/admin/need_reboot`
|
||||||
|
|
||||||
|
### Returns the flag whether the pleroma should be restarted
|
||||||
|
|
||||||
|
- Params: none
|
||||||
|
- Response:
|
||||||
|
- `need_reboot` - boolean
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"need_reboot": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## `GET /api/pleroma/admin/config`
|
## `GET /api/pleroma/admin/config`
|
||||||
|
|
||||||
### Get list of merged default settings with saved in database.
|
### Get list of merged default settings with saved in database.
|
||||||
|
|
||||||
*If `need_reboot` flag exists in response, instance must be restarted, so reboot time settings can take effect.*
|
*If `need_reboot` is `true`, instance must be restarted, so reboot time settings can take effect.*
|
||||||
|
|
||||||
**Only works when configuration from database is enabled.**
|
**Only works when configuration from database is enabled.**
|
||||||
|
|
||||||
|
@ -821,13 +836,12 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
"need_reboot": true
|
"need_reboot": true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
need_reboot - *optional*, if were changed reboot time settings.
|
|
||||||
|
|
||||||
## `POST /api/pleroma/admin/config`
|
## `POST /api/pleroma/admin/config`
|
||||||
|
|
||||||
### Update config settings
|
### Update config settings
|
||||||
|
|
||||||
*If `need_reboot` flag exists in response, instance must be restarted, so reboot time settings can take effect.*
|
*If `need_reboot` is `true`, instance must be restarted, so reboot time settings can take effect.*
|
||||||
|
|
||||||
**Only works when configuration from database is enabled.**
|
**Only works when configuration from database is enabled.**
|
||||||
|
|
||||||
|
@ -971,7 +985,6 @@ config :quack,
|
||||||
"need_reboot": true
|
"need_reboot": true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
need_reboot - *optional*, if were changed reboot time settings.
|
|
||||||
|
|
||||||
## ` GET /api/pleroma/admin/config/descriptions`
|
## ` GET /api/pleroma/admin/config/descriptions`
|
||||||
|
|
||||||
|
@ -1075,3 +1088,104 @@ Loads json generated from `config/descriptions.exs`.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `GET /api/pleroma/admin/oauth_app`
|
||||||
|
|
||||||
|
### List OAuth app
|
||||||
|
|
||||||
|
- Params:
|
||||||
|
- *optional* `name`
|
||||||
|
- *optional* `client_id`
|
||||||
|
- *optional* `page`
|
||||||
|
- *optional* `page_size`
|
||||||
|
- *optional* `trusted`
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"apps": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "App name",
|
||||||
|
"client_id": "yHoDSiWYp5mPV6AfsaVOWjdOyt5PhWRiafi6MRd1lSk",
|
||||||
|
"client_secret": "nLmis486Vqrv2o65eM9mLQx_m_4gH-Q6PcDpGIMl6FY",
|
||||||
|
"redirect_uri": "https://example.com/oauth-callback",
|
||||||
|
"website": "https://example.com",
|
||||||
|
"trusted": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"count": 17,
|
||||||
|
"page_size": 50
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## `POST /api/pleroma/admin/oauth_app`
|
||||||
|
|
||||||
|
### Create OAuth App
|
||||||
|
|
||||||
|
- Params:
|
||||||
|
- `name`
|
||||||
|
- `redirect_uris`
|
||||||
|
- `scopes`
|
||||||
|
- *optional* `website`
|
||||||
|
- *optional* `trusted`
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "App name",
|
||||||
|
"client_id": "yHoDSiWYp5mPV6AfsaVOWjdOyt5PhWRiafi6MRd1lSk",
|
||||||
|
"client_secret": "nLmis486Vqrv2o65eM9mLQx_m_4gH-Q6PcDpGIMl6FY",
|
||||||
|
"redirect_uri": "https://example.com/oauth-callback",
|
||||||
|
"website": "https://example.com",
|
||||||
|
"trusted": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- On failure:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"redirect_uris": "can't be blank",
|
||||||
|
"name": "can't be blank"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `PATCH /api/pleroma/admin/oauth_app/:id`
|
||||||
|
|
||||||
|
### Update OAuth App
|
||||||
|
|
||||||
|
- Params:
|
||||||
|
- *optional* `name`
|
||||||
|
- *optional* `redirect_uris`
|
||||||
|
- *optional* `scopes`
|
||||||
|
- *optional* `website`
|
||||||
|
- *optional* `trusted`
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "App name",
|
||||||
|
"client_id": "yHoDSiWYp5mPV6AfsaVOWjdOyt5PhWRiafi6MRd1lSk",
|
||||||
|
"client_secret": "nLmis486Vqrv2o65eM9mLQx_m_4gH-Q6PcDpGIMl6FY",
|
||||||
|
"redirect_uri": "https://example.com/oauth-callback",
|
||||||
|
"website": "https://example.com",
|
||||||
|
"trusted": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `DELETE /api/pleroma/admin/oauth_app/:id`
|
||||||
|
|
||||||
|
### Delete OAuth App
|
||||||
|
|
||||||
|
- Params: None
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
- On success: `204`, empty response
|
||||||
|
- On failure:
|
||||||
|
- 400 Bad Request `"Invalid parameters"` when `status` is missing
|
16
docs/administration/CLI_tasks/oauth_app.md
Normal file
16
docs/administration/CLI_tasks/oauth_app.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Creating trusted OAuth App
|
||||||
|
|
||||||
|
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||||
|
|
||||||
|
## Create trusted OAuth App.
|
||||||
|
|
||||||
|
Optional params:
|
||||||
|
* `-s SCOPES` - scopes for app, e.g. `read,write,follow,push`.
|
||||||
|
|
||||||
|
```sh tab="OTP"
|
||||||
|
./bin/pleroma_ctl app create -n APP_NAME -r REDIRECT_URI
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.app create -n APP_NAME -r REDIRECT_URI
|
||||||
|
```
|
|
@ -113,7 +113,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RewritePolicy do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def describe do
|
def describe do
|
||||||
{:ok, %{mrf_sample: %{content: "new message content"}}}`
|
{:ok, %{mrf_sample: %{content: "new message content"}}}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
49
lib/mix/tasks/pleroma/app.ex
Normal file
49
lib/mix/tasks/pleroma/app.ex
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Mix.Tasks.Pleroma.App do
|
||||||
|
@moduledoc File.read!("docs/administration/CLI_tasks/oauth_app.md")
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
import Mix.Pleroma
|
||||||
|
|
||||||
|
@shortdoc "Creates trusted OAuth App"
|
||||||
|
|
||||||
|
def run(["create" | options]) do
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
{opts, _} =
|
||||||
|
OptionParser.parse!(options,
|
||||||
|
strict: [name: :string, redirect_uri: :string, scopes: :string],
|
||||||
|
aliases: [n: :name, r: :redirect_uri, s: :scopes]
|
||||||
|
)
|
||||||
|
|
||||||
|
scopes =
|
||||||
|
if opts[:scopes] do
|
||||||
|
String.split(opts[:scopes], ",")
|
||||||
|
else
|
||||||
|
["read", "write", "follow", "push"]
|
||||||
|
end
|
||||||
|
|
||||||
|
params = %{
|
||||||
|
client_name: opts[:name],
|
||||||
|
redirect_uris: opts[:redirect_uri],
|
||||||
|
trusted: true,
|
||||||
|
scopes: scopes
|
||||||
|
}
|
||||||
|
|
||||||
|
with {:ok, app} <- Pleroma.Web.OAuth.App.create(params) do
|
||||||
|
shell_info("#{app.client_name} successfully created:")
|
||||||
|
shell_info("App client_id: " <> app.client_id)
|
||||||
|
shell_info("App client_secret: " <> app.client_secret)
|
||||||
|
else
|
||||||
|
{:error, changeset} ->
|
||||||
|
shell_error("Creating failed:")
|
||||||
|
|
||||||
|
Enum.each(Pleroma.Web.OAuth.App.errors(changeset), fn {key, error} ->
|
||||||
|
shell_error("#{key}: #{error}")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -67,7 +67,8 @@ def run(["render_timeline", nickname | _] = args) do
|
||||||
Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
|
Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
|
||||||
activities: activities,
|
activities: activities,
|
||||||
for: user,
|
for: user,
|
||||||
as: :activity
|
as: :activity,
|
||||||
|
skip_relationships: true
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
|
|
|
@ -54,10 +54,19 @@ def load_and_update_env(deleted_settings \\ [], restart_pleroma? \\ true) do
|
||||||
[:pleroma, nil, :prometheus]
|
[:pleroma, nil, :prometheus]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
{logger, other} =
|
||||||
|
(Repo.all(ConfigDB) ++ deleted_settings)
|
||||||
|
|> Enum.map(&transform_and_merge/1)
|
||||||
|
|> Enum.split_with(fn {group, _, _, _} -> group in [:logger, :quack] end)
|
||||||
|
|
||||||
|
logger
|
||||||
|
|> Enum.sort()
|
||||||
|
|> Enum.each(&configure/1)
|
||||||
|
|
||||||
started_applications = Application.started_applications()
|
started_applications = Application.started_applications()
|
||||||
|
|
||||||
(Repo.all(ConfigDB) ++ deleted_settings)
|
other
|
||||||
|> Enum.map(&merge_and_update/1)
|
|> Enum.map(&update/1)
|
||||||
|> Enum.uniq()
|
|> Enum.uniq()
|
||||||
|> Enum.reject(&(&1 in reject_restart))
|
|> Enum.reject(&(&1 in reject_restart))
|
||||||
|> maybe_set_pleroma_last()
|
|> maybe_set_pleroma_last()
|
||||||
|
@ -81,51 +90,71 @@ defp maybe_set_pleroma_last(apps) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp group_for_restart(:logger, key, _, merged_value) do
|
defp transform_and_merge(%{group: group, key: key, value: value} = setting) do
|
||||||
# change logger configuration in runtime, without restart
|
group = ConfigDB.from_string(group)
|
||||||
if Keyword.keyword?(merged_value) and
|
key = ConfigDB.from_string(key)
|
||||||
key not in [:compile_time_application, :backends, :compile_time_purge_matching] do
|
value = ConfigDB.from_binary(value)
|
||||||
Logger.configure_backend(key, merged_value)
|
|
||||||
else
|
|
||||||
Logger.configure([{key, merged_value}])
|
|
||||||
end
|
|
||||||
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
defp group_for_restart(group, _, _, _) when group != :pleroma, do: group
|
|
||||||
|
|
||||||
defp group_for_restart(group, key, value, _) do
|
|
||||||
if pleroma_need_restart?(group, key, value), do: group
|
|
||||||
end
|
|
||||||
|
|
||||||
defp merge_and_update(setting) do
|
|
||||||
try do
|
|
||||||
key = ConfigDB.from_string(setting.key)
|
|
||||||
group = ConfigDB.from_string(setting.group)
|
|
||||||
|
|
||||||
default = Config.Holder.default_config(group, key)
|
default = Config.Holder.default_config(group, key)
|
||||||
value = ConfigDB.from_binary(setting.value)
|
|
||||||
|
|
||||||
merged_value =
|
merged =
|
||||||
cond do
|
cond do
|
||||||
Ecto.get_meta(setting, :state) == :deleted -> default
|
Ecto.get_meta(setting, :state) == :deleted -> default
|
||||||
can_be_merged?(default, value) -> ConfigDB.merge_group(group, key, default, value)
|
can_be_merged?(default, value) -> ConfigDB.merge_group(group, key, default, value)
|
||||||
true -> value
|
true -> value
|
||||||
end
|
end
|
||||||
|
|
||||||
:ok = update_env(group, key, merged_value)
|
{group, key, value, merged}
|
||||||
|
end
|
||||||
|
|
||||||
group_for_restart(group, key, value, merged_value)
|
# change logger configuration in runtime, without restart
|
||||||
|
defp configure({:quack, key, _, merged}) do
|
||||||
|
Logger.configure_backend(Quack.Logger, [{key, merged}])
|
||||||
|
:ok = update_env(:quack, key, merged)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp configure({_, :backends, _, merged}) do
|
||||||
|
# removing current backends
|
||||||
|
Enum.each(Application.get_env(:logger, :backends), &Logger.remove_backend/1)
|
||||||
|
|
||||||
|
Enum.each(merged, &Logger.add_backend/1)
|
||||||
|
|
||||||
|
:ok = update_env(:logger, :backends, merged)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp configure({_, key, _, merged}) when key in [:console, :ex_syslogger] do
|
||||||
|
merged =
|
||||||
|
if key == :console do
|
||||||
|
put_in(merged[:format], merged[:format] <> "\n")
|
||||||
|
else
|
||||||
|
merged
|
||||||
|
end
|
||||||
|
|
||||||
|
backend =
|
||||||
|
if key == :ex_syslogger,
|
||||||
|
do: {ExSyslogger, :ex_syslogger},
|
||||||
|
else: key
|
||||||
|
|
||||||
|
Logger.configure_backend(backend, merged)
|
||||||
|
:ok = update_env(:logger, key, merged)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp configure({_, key, _, merged}) do
|
||||||
|
Logger.configure([{key, merged}])
|
||||||
|
:ok = update_env(:logger, key, merged)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp update({group, key, value, merged}) do
|
||||||
|
try do
|
||||||
|
:ok = update_env(group, key, merged)
|
||||||
|
|
||||||
|
if group != :pleroma or pleroma_need_restart?(group, key, value), do: group
|
||||||
rescue
|
rescue
|
||||||
error ->
|
error ->
|
||||||
error_msg =
|
error_msg =
|
||||||
"updating env causes error, group: " <>
|
"updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{
|
||||||
inspect(setting.group) <>
|
inspect(value)
|
||||||
" key: " <>
|
} error: #{inspect(error)}"
|
||||||
inspect(setting.key) <>
|
|
||||||
" value: " <>
|
|
||||||
inspect(ConfigDB.from_binary(setting.value)) <> " error: " <> inspect(error)
|
|
||||||
|
|
||||||
Logger.warn(error_msg)
|
Logger.warn(error_msg)
|
||||||
|
|
||||||
|
@ -133,6 +162,9 @@ defp merge_and_update(setting) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp update_env(group, key, nil), do: Application.delete_env(group, key)
|
||||||
|
defp update_env(group, key, value), do: Application.put_env(group, key, value)
|
||||||
|
|
||||||
@spec pleroma_need_restart?(atom(), atom(), any()) :: boolean()
|
@spec pleroma_need_restart?(atom(), atom(), any()) :: boolean()
|
||||||
def pleroma_need_restart?(group, key, value) do
|
def pleroma_need_restart?(group, key, value) do
|
||||||
group_and_key_need_reboot?(group, key) or group_and_subkey_need_reboot?(group, key, value)
|
group_and_key_need_reboot?(group, key) or group_and_subkey_need_reboot?(group, key, value)
|
||||||
|
@ -150,9 +182,6 @@ defp group_and_subkey_need_reboot?(group, key, value) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp update_env(group, key, nil), do: Application.delete_env(group, key)
|
|
||||||
defp update_env(group, key, value), do: Application.put_env(group, key, value)
|
|
||||||
|
|
||||||
defp restart(_, :pleroma, env), do: Restarter.Pleroma.restart_after_boot(env)
|
defp restart(_, :pleroma, env), do: Restarter.Pleroma.restart_after_boot(env)
|
||||||
|
|
||||||
defp restart(started_applications, app, _) do
|
defp restart(started_applications, app, _) do
|
||||||
|
|
|
@ -38,22 +38,14 @@ def demojify(text) do
|
||||||
|
|
||||||
def demojify(text, nil), do: text
|
def demojify(text, nil), do: text
|
||||||
|
|
||||||
@doc "Outputs a list of the emoji-shortcodes in a text"
|
|
||||||
def get_emoji(text) when is_binary(text) do
|
|
||||||
Enum.filter(Emoji.get_all(), fn {emoji, %Emoji{}} ->
|
|
||||||
String.contains?(text, ":#{emoji}:")
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_emoji(_), do: []
|
|
||||||
|
|
||||||
@doc "Outputs a list of the emoji-Maps in a text"
|
@doc "Outputs a list of the emoji-Maps in a text"
|
||||||
def get_emoji_map(text) when is_binary(text) do
|
def get_emoji_map(text) when is_binary(text) do
|
||||||
get_emoji(text)
|
Emoji.get_all()
|
||||||
|
|> Enum.filter(fn {emoji, %Emoji{}} -> String.contains?(text, ":#{emoji}:") end)
|
||||||
|> Enum.reduce(%{}, fn {name, %Emoji{file: file}}, acc ->
|
|> Enum.reduce(%{}, fn {name, %Emoji{file: file}}, acc ->
|
||||||
Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
|
Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_emoji_map(_), do: []
|
def get_emoji_map(_), do: %{}
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,7 +31,7 @@ def escape_mention_handler("@" <> nickname = mention, buffer, _, _) do
|
||||||
def mention_handler("@" <> nickname, buffer, opts, acc) do
|
def mention_handler("@" <> nickname, buffer, opts, acc) do
|
||||||
case User.get_cached_by_nickname(nickname) do
|
case User.get_cached_by_nickname(nickname) do
|
||||||
%User{id: id} = user ->
|
%User{id: id} = user ->
|
||||||
ap_id = get_ap_id(user)
|
user_url = user.uri || user.ap_id
|
||||||
nickname_text = get_nickname_text(nickname, opts)
|
nickname_text = get_nickname_text(nickname, opts)
|
||||||
|
|
||||||
link =
|
link =
|
||||||
|
@ -42,7 +42,7 @@ def mention_handler("@" <> nickname, buffer, opts, acc) do
|
||||||
["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)],
|
["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)],
|
||||||
"data-user": id,
|
"data-user": id,
|
||||||
class: "u-url mention",
|
class: "u-url mention",
|
||||||
href: ap_id,
|
href: user_url,
|
||||||
rel: "ugc"
|
rel: "ugc"
|
||||||
),
|
),
|
||||||
class: "h-card"
|
class: "h-card"
|
||||||
|
@ -146,9 +146,6 @@ def truncate(text, max_length \\ 200, omission \\ "...") do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_ap_id(%User{source_data: %{"url" => url}}) when is_binary(url), do: url
|
|
||||||
defp get_ap_id(%User{ap_id: ap_id}), do: ap_id
|
|
||||||
|
|
||||||
defp get_nickname_text(nickname, %{mentions_format: :full}), do: User.full_nickname(nickname)
|
defp get_nickname_text(nickname, %{mentions_format: :full}), do: User.full_nickname(nickname)
|
||||||
defp get_nickname_text(nickname, _), do: User.local_nickname(nickname)
|
defp get_nickname_text(nickname, _), do: User.local_nickname(nickname)
|
||||||
end
|
end
|
||||||
|
|
17
lib/pleroma/plugs/auth_expected_plug.ex
Normal file
17
lib/pleroma/plugs/auth_expected_plug.ex
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Plugs.AuthExpectedPlug do
|
||||||
|
import Plug.Conn
|
||||||
|
|
||||||
|
def init(options), do: options
|
||||||
|
|
||||||
|
def call(conn, _) do
|
||||||
|
put_private(conn, :auth_expected, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def auth_expected?(conn) do
|
||||||
|
conn.private[:auth_expected]
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,8 +4,11 @@
|
||||||
|
|
||||||
defmodule Pleroma.Plugs.AuthenticationPlug do
|
defmodule Pleroma.Plugs.AuthenticationPlug do
|
||||||
alias Comeonin.Pbkdf2
|
alias Comeonin.Pbkdf2
|
||||||
import Plug.Conn
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
|
import Plug.Conn
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
def init(options), do: options
|
def init(options), do: options
|
||||||
|
@ -37,6 +40,7 @@ def call(
|
||||||
if Pbkdf2.checkpw(password, password_hash) do
|
if Pbkdf2.checkpw(password, password_hash) do
|
||||||
conn
|
conn
|
||||||
|> assign(:user, auth_user)
|
|> assign(:user, auth_user)
|
||||||
|
|> OAuthScopesPlug.skip_plug()
|
||||||
else
|
else
|
||||||
conn
|
conn
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
defmodule Pleroma.Plugs.LegacyAuthenticationPlug do
|
defmodule Pleroma.Plugs.LegacyAuthenticationPlug do
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
|
||||||
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
def init(options) do
|
def init(options) do
|
||||||
|
@ -27,6 +29,7 @@ def call(
|
||||||
conn
|
conn
|
||||||
|> assign(:auth_user, user)
|
|> assign(:auth_user, user)
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
|
|> OAuthScopesPlug.skip_plug()
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -42,13 +42,13 @@ def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = con
|
||||||
else
|
else
|
||||||
{:user_match, false} ->
|
{:user_match, false} ->
|
||||||
Logger.debug("Failed to map identity from signature (payload actor mismatch)")
|
Logger.debug("Failed to map identity from signature (payload actor mismatch)")
|
||||||
Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
|
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{inspect(actor)}")
|
||||||
assign(conn, :valid_signature, false)
|
assign(conn, :valid_signature, false)
|
||||||
|
|
||||||
# remove me once testsuite uses mapped capabilities instead of what we do now
|
# remove me once testsuite uses mapped capabilities instead of what we do now
|
||||||
{:user, nil} ->
|
{:user, nil} ->
|
||||||
Logger.debug("Failed to map identity from signature (lookup failure)")
|
Logger.debug("Failed to map identity from signature (lookup failure)")
|
||||||
Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
|
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")
|
||||||
conn
|
conn
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -60,7 +60,7 @@ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
Logger.debug("Failed to map identity from signature (no payload actor mismatch)")
|
Logger.debug("Failed to map identity from signature (no payload actor mismatch)")
|
||||||
Logger.debug("key_id=#{key_id_from_conn(conn)}")
|
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}")
|
||||||
assign(conn, :valid_signature, false)
|
assign(conn, :valid_signature, false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,12 +8,15 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do
|
||||||
|
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
|
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
|
||||||
|
alias Pleroma.Plugs.PlugHelper
|
||||||
|
|
||||||
|
use Pleroma.Web, :plug
|
||||||
|
|
||||||
@behaviour Plug
|
@behaviour Plug
|
||||||
|
|
||||||
def init(%{scopes: _} = options), do: options
|
def init(%{scopes: _} = options), do: options
|
||||||
|
|
||||||
def call(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
|
def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
|
||||||
op = options[:op] || :|
|
op = options[:op] || :|
|
||||||
token = assigns[:token]
|
token = assigns[:token]
|
||||||
|
|
||||||
|
|
40
lib/pleroma/plugs/plug_helper.ex
Normal file
40
lib/pleroma/plugs/plug_helper.ex
Normal 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.Plugs.PlugHelper do
|
||||||
|
@moduledoc "Pleroma Plug helper"
|
||||||
|
|
||||||
|
@called_plugs_list_id :called_plugs
|
||||||
|
def called_plugs_list_id, do: @called_plugs_list_id
|
||||||
|
|
||||||
|
@skipped_plugs_list_id :skipped_plugs
|
||||||
|
def skipped_plugs_list_id, do: @skipped_plugs_list_id
|
||||||
|
|
||||||
|
@doc "Returns `true` if specified plug was called."
|
||||||
|
def plug_called?(conn, plug_module) do
|
||||||
|
contained_in_private_list?(conn, @called_plugs_list_id, plug_module)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "Returns `true` if specified plug was explicitly marked as skipped."
|
||||||
|
def plug_skipped?(conn, plug_module) do
|
||||||
|
contained_in_private_list?(conn, @skipped_plugs_list_id, plug_module)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "Returns `true` if specified plug was either called or explicitly marked as skipped."
|
||||||
|
def plug_called_or_skipped?(conn, plug_module) do
|
||||||
|
plug_called?(conn, plug_module) || plug_skipped?(conn, plug_module)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Appends plug to known list (skipped, called). Intended to be used from within plug code only.
|
||||||
|
def append_to_private_list(conn, list_id, value) do
|
||||||
|
list = conn.private[list_id] || []
|
||||||
|
modified_list = Enum.uniq(list ++ [value])
|
||||||
|
Plug.Conn.put_private(conn, list_id, modified_list)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp contained_in_private_list?(conn, private_variable, value) do
|
||||||
|
list = conn.private[private_variable] || []
|
||||||
|
value in list
|
||||||
|
end
|
||||||
|
end
|
|
@ -110,20 +110,9 @@ defp handle(conn, action_settings) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def disabled?(conn) do
|
def disabled?(conn) do
|
||||||
localhost_or_socket =
|
|
||||||
case Config.get([Pleroma.Web.Endpoint, :http, :ip]) do
|
|
||||||
{127, 0, 0, 1} -> true
|
|
||||||
{0, 0, 0, 0, 0, 0, 0, 1} -> true
|
|
||||||
{:local, _} -> true
|
|
||||||
_ -> false
|
|
||||||
end
|
|
||||||
|
|
||||||
remote_ip_not_found =
|
|
||||||
if Map.has_key?(conn.assigns, :remote_ip_found),
|
if Map.has_key?(conn.assigns, :remote_ip_found),
|
||||||
do: !conn.assigns.remote_ip_found,
|
do: !conn.assigns.remote_ip_found,
|
||||||
else: false
|
else: false
|
||||||
|
|
||||||
localhost_or_socket and remote_ip_not_found
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@inspect_bucket_not_found {:error, :not_found}
|
@inspect_bucket_not_found {:error, :not_found}
|
||||||
|
|
|
@ -7,8 +7,6 @@ defmodule Pleroma.Plugs.RemoteIp do
|
||||||
This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
|
This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import Plug.Conn
|
|
||||||
|
|
||||||
@behaviour Plug
|
@behaviour Plug
|
||||||
|
|
||||||
@headers ~w[
|
@headers ~w[
|
||||||
|
@ -28,12 +26,11 @@ defmodule Pleroma.Plugs.RemoteIp do
|
||||||
|
|
||||||
def init(_), do: nil
|
def init(_), do: nil
|
||||||
|
|
||||||
def call(%{remote_ip: original_remote_ip} = conn, _) do
|
def call(conn, _) do
|
||||||
config = Pleroma.Config.get(__MODULE__, [])
|
config = Pleroma.Config.get(__MODULE__, [])
|
||||||
|
|
||||||
if Keyword.get(config, :enabled, false) do
|
if Keyword.get(config, :enabled, false) do
|
||||||
%{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts(config))
|
RemoteIp.call(conn, remote_ip_opts(config))
|
||||||
assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip)
|
|
||||||
else
|
else
|
||||||
conn
|
conn
|
||||||
end
|
end
|
||||||
|
|
|
@ -41,6 +41,7 @@ def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
|
||||||
conn ->
|
conn ->
|
||||||
conn
|
conn
|
||||||
end
|
end
|
||||||
|
|> merge_resp_headers([{"content-security-policy", "sandbox"}])
|
||||||
|
|
||||||
config = Pleroma.Config.get(Pleroma.Upload)
|
config = Pleroma.Config.get(Pleroma.Upload)
|
||||||
|
|
||||||
|
|
|
@ -243,7 +243,7 @@ def handle_info({:gun_down, conn_pid, _protocol, _reason, _killed}, state) do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info({:DOWN, _ref, :process, conn_pid, reason}, state) do
|
def handle_info({:DOWN, _ref, :process, conn_pid, reason}, state) do
|
||||||
Logger.debug("received DOWM message for #{inspect(conn_pid)} reason -> #{inspect(reason)}")
|
Logger.debug("received DOWN message for #{inspect(conn_pid)} reason -> #{inspect(reason)}")
|
||||||
|
|
||||||
state =
|
state =
|
||||||
with {key, conn} <- find_conn(state.conns, conn_pid) do
|
with {key, conn} <- find_conn(state.conns, conn_pid) do
|
||||||
|
|
31
lib/pleroma/tests/oauth_test_controller.ex
Normal file
31
lib/pleroma/tests/oauth_test_controller.ex
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
# A test controller reachable only in :test env.
|
||||||
|
# Serves to test OAuth scopes check skipping / enforcement.
|
||||||
|
defmodule Pleroma.Tests.OAuthTestController do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
|
|
||||||
|
plug(:skip_plug, OAuthScopesPlug when action == :skipped_oauth)
|
||||||
|
|
||||||
|
plug(OAuthScopesPlug, %{scopes: ["read"]} when action != :missed_oauth)
|
||||||
|
|
||||||
|
def skipped_oauth(conn, _params) do
|
||||||
|
noop(conn)
|
||||||
|
end
|
||||||
|
|
||||||
|
def performed_oauth(conn, _params) do
|
||||||
|
noop(conn)
|
||||||
|
end
|
||||||
|
|
||||||
|
def missed_oauth(conn, _params) do
|
||||||
|
noop(conn)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp noop(conn), do: json(conn, %{})
|
||||||
|
end
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.User do
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
alias Pleroma.Delivery
|
alias Pleroma.Delivery
|
||||||
|
alias Pleroma.Emoji
|
||||||
alias Pleroma.FollowingRelationship
|
alias Pleroma.FollowingRelationship
|
||||||
alias Pleroma.Formatter
|
alias Pleroma.Formatter
|
||||||
alias Pleroma.HTML
|
alias Pleroma.HTML
|
||||||
|
@ -28,6 +29,7 @@ defmodule Pleroma.User do
|
||||||
alias Pleroma.UserRelationship
|
alias Pleroma.UserRelationship
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
|
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
|
||||||
|
@ -82,6 +84,7 @@ defmodule Pleroma.User do
|
||||||
field(:password, :string, virtual: true)
|
field(:password, :string, virtual: true)
|
||||||
field(:password_confirmation, :string, virtual: true)
|
field(:password_confirmation, :string, virtual: true)
|
||||||
field(:keys, :string)
|
field(:keys, :string)
|
||||||
|
field(:public_key, :string)
|
||||||
field(:ap_id, :string)
|
field(:ap_id, :string)
|
||||||
field(:avatar, :map)
|
field(:avatar, :map)
|
||||||
field(:local, :boolean, default: true)
|
field(:local, :boolean, default: true)
|
||||||
|
@ -94,7 +97,6 @@ defmodule Pleroma.User do
|
||||||
field(:last_digest_emailed_at, :naive_datetime)
|
field(:last_digest_emailed_at, :naive_datetime)
|
||||||
field(:banner, :map, default: %{})
|
field(:banner, :map, default: %{})
|
||||||
field(:background, :map, default: %{})
|
field(:background, :map, default: %{})
|
||||||
field(:source_data, :map, default: %{})
|
|
||||||
field(:note_count, :integer, default: 0)
|
field(:note_count, :integer, default: 0)
|
||||||
field(:follower_count, :integer, default: 0)
|
field(:follower_count, :integer, default: 0)
|
||||||
field(:following_count, :integer, default: 0)
|
field(:following_count, :integer, default: 0)
|
||||||
|
@ -112,7 +114,7 @@ defmodule Pleroma.User do
|
||||||
field(:show_role, :boolean, default: true)
|
field(:show_role, :boolean, default: true)
|
||||||
field(:settings, :map, default: nil)
|
field(:settings, :map, default: nil)
|
||||||
field(:magic_key, :string, default: nil)
|
field(:magic_key, :string, default: nil)
|
||||||
field(:uri, :string, default: nil)
|
field(:uri, Types.Uri, default: nil)
|
||||||
field(:hide_followers_count, :boolean, default: false)
|
field(:hide_followers_count, :boolean, default: false)
|
||||||
field(:hide_follows_count, :boolean, default: false)
|
field(:hide_follows_count, :boolean, default: false)
|
||||||
field(:hide_followers, :boolean, default: false)
|
field(:hide_followers, :boolean, default: false)
|
||||||
|
@ -122,7 +124,7 @@ defmodule Pleroma.User do
|
||||||
field(:pinned_activities, {:array, :string}, default: [])
|
field(:pinned_activities, {:array, :string}, default: [])
|
||||||
field(:email_notifications, :map, default: %{"digest" => false})
|
field(:email_notifications, :map, default: %{"digest" => false})
|
||||||
field(:mascot, :map, default: nil)
|
field(:mascot, :map, default: nil)
|
||||||
field(:emoji, {:array, :map}, default: [])
|
field(:emoji, :map, default: %{})
|
||||||
field(:pleroma_settings_store, :map, default: %{})
|
field(:pleroma_settings_store, :map, default: %{})
|
||||||
field(:fields, {:array, :map}, default: [])
|
field(:fields, {:array, :map}, default: [])
|
||||||
field(:raw_fields, {:array, :map}, default: [])
|
field(:raw_fields, {:array, :map}, default: [])
|
||||||
|
@ -132,6 +134,8 @@ defmodule Pleroma.User do
|
||||||
field(:skip_thread_containment, :boolean, default: false)
|
field(:skip_thread_containment, :boolean, default: false)
|
||||||
field(:actor_type, :string, default: "Person")
|
field(:actor_type, :string, default: "Person")
|
||||||
field(:also_known_as, {:array, :string}, default: [])
|
field(:also_known_as, {:array, :string}, default: [])
|
||||||
|
field(:inbox, :string)
|
||||||
|
field(:shared_inbox, :string)
|
||||||
|
|
||||||
embeds_one(
|
embeds_one(
|
||||||
:notification_settings,
|
:notification_settings,
|
||||||
|
@ -306,6 +310,7 @@ def banner_url(user, options \\ []) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Should probably be renamed or removed
|
||||||
def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
|
def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
|
||||||
|
|
||||||
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
|
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
|
||||||
|
@ -339,32 +344,53 @@ defp truncate_if_exists(params, key, max_length) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def remote_user_creation(params) do
|
defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
|
||||||
|
|
||||||
|
defp fix_follower_address(%{nickname: nickname} = params),
|
||||||
|
do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
|
||||||
|
|
||||||
|
defp fix_follower_address(params), do: params
|
||||||
|
|
||||||
|
def remote_user_changeset(struct \\ %User{local: false}, params) do
|
||||||
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||||
|
|
||||||
|
name =
|
||||||
|
case params[:name] do
|
||||||
|
name when is_binary(name) and byte_size(name) > 0 -> name
|
||||||
|
_ -> params[:nickname]
|
||||||
|
end
|
||||||
|
|
||||||
params =
|
params =
|
||||||
params
|
params
|
||||||
|
|> Map.put(:name, name)
|
||||||
|
|> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
|
||||||
|> truncate_if_exists(:name, name_limit)
|
|> truncate_if_exists(:name, name_limit)
|
||||||
|> truncate_if_exists(:bio, bio_limit)
|
|> truncate_if_exists(:bio, bio_limit)
|
||||||
|> truncate_fields_param()
|
|> truncate_fields_param()
|
||||||
|
|> fix_follower_address()
|
||||||
|
|
||||||
changeset =
|
struct
|
||||||
%User{local: false}
|
|
||||||
|> cast(
|
|> cast(
|
||||||
params,
|
params,
|
||||||
[
|
[
|
||||||
:bio,
|
:bio,
|
||||||
:name,
|
:name,
|
||||||
|
:emoji,
|
||||||
:ap_id,
|
:ap_id,
|
||||||
|
:inbox,
|
||||||
|
:shared_inbox,
|
||||||
:nickname,
|
:nickname,
|
||||||
|
:public_key,
|
||||||
:avatar,
|
:avatar,
|
||||||
:ap_enabled,
|
:ap_enabled,
|
||||||
:source_data,
|
|
||||||
:banner,
|
:banner,
|
||||||
:locked,
|
:locked,
|
||||||
|
:last_refreshed_at,
|
||||||
:magic_key,
|
:magic_key,
|
||||||
:uri,
|
:uri,
|
||||||
|
:follower_address,
|
||||||
|
:following_address,
|
||||||
:hide_followers,
|
:hide_followers,
|
||||||
:hide_follows,
|
:hide_follows,
|
||||||
:hide_followers_count,
|
:hide_followers_count,
|
||||||
|
@ -384,17 +410,6 @@ def remote_user_creation(params) do
|
||||||
|> validate_length(:bio, max: bio_limit)
|
|> validate_length(:bio, max: bio_limit)
|
||||||
|> validate_length(:name, max: name_limit)
|
|> validate_length(:name, max: name_limit)
|
||||||
|> validate_fields(true)
|
|> validate_fields(true)
|
||||||
|
|
||||||
case params[:source_data] do
|
|
||||||
%{"followers" => followers, "following" => following} ->
|
|
||||||
changeset
|
|
||||||
|> put_change(:follower_address, followers)
|
|
||||||
|> put_change(:following_address, following)
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
|
|
||||||
put_change(changeset, :follower_address, followers)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_changeset(struct, params \\ %{}) do
|
def update_changeset(struct, params \\ %{}) do
|
||||||
|
@ -407,7 +422,11 @@ def update_changeset(struct, params \\ %{}) do
|
||||||
[
|
[
|
||||||
:bio,
|
:bio,
|
||||||
:name,
|
:name,
|
||||||
|
:emoji,
|
||||||
:avatar,
|
:avatar,
|
||||||
|
:public_key,
|
||||||
|
:inbox,
|
||||||
|
:shared_inbox,
|
||||||
:locked,
|
:locked,
|
||||||
:no_rich_text,
|
:no_rich_text,
|
||||||
:default_scope,
|
:default_scope,
|
||||||
|
@ -434,6 +453,7 @@ def update_changeset(struct, params \\ %{}) do
|
||||||
|> validate_length(:bio, max: bio_limit)
|
|> validate_length(:bio, max: bio_limit)
|
||||||
|> validate_length(:name, min: 1, max: name_limit)
|
|> validate_length(:name, min: 1, max: name_limit)
|
||||||
|> put_fields()
|
|> put_fields()
|
||||||
|
|> put_emoji()
|
||||||
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
|
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
|
||||||
|> put_change_if_present(:avatar, &put_upload(&1, :avatar))
|
|> put_change_if_present(:avatar, &put_upload(&1, :avatar))
|
||||||
|> put_change_if_present(:banner, &put_upload(&1, :banner))
|
|> put_change_if_present(:banner, &put_upload(&1, :banner))
|
||||||
|
@ -469,6 +489,18 @@ defp parse_fields(value) do
|
||||||
|> elem(0)
|
|> elem(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp put_emoji(changeset) do
|
||||||
|
bio = get_change(changeset, :bio)
|
||||||
|
name = get_change(changeset, :name)
|
||||||
|
|
||||||
|
if bio || name do
|
||||||
|
emoji = Map.merge(Emoji.Formatter.get_emoji_map(bio), Emoji.Formatter.get_emoji_map(name))
|
||||||
|
put_change(changeset, :emoji, emoji)
|
||||||
|
else
|
||||||
|
changeset
|
||||||
|
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
|
if value = get_change(changeset, map_field) do
|
||||||
with {:ok, new_value} <- value_function.(value) do
|
with {:ok, new_value} <- value_function.(value) do
|
||||||
|
@ -488,49 +520,6 @@ defp put_upload(value, type) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
|
|
||||||
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
|
||||||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
|
||||||
|
|
||||||
params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
|
|
||||||
|
|
||||||
params = if remote?, do: truncate_fields_param(params), else: params
|
|
||||||
|
|
||||||
struct
|
|
||||||
|> cast(
|
|
||||||
params,
|
|
||||||
[
|
|
||||||
:bio,
|
|
||||||
:name,
|
|
||||||
:follower_address,
|
|
||||||
:following_address,
|
|
||||||
:avatar,
|
|
||||||
:last_refreshed_at,
|
|
||||||
:ap_enabled,
|
|
||||||
:source_data,
|
|
||||||
:banner,
|
|
||||||
:locked,
|
|
||||||
:magic_key,
|
|
||||||
:follower_count,
|
|
||||||
:following_count,
|
|
||||||
:hide_follows,
|
|
||||||
:fields,
|
|
||||||
:hide_followers,
|
|
||||||
:allow_following_move,
|
|
||||||
:discoverable,
|
|
||||||
:hide_followers_count,
|
|
||||||
:hide_follows_count,
|
|
||||||
:actor_type,
|
|
||||||
:also_known_as
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|> unique_constraint(:nickname)
|
|
||||||
|> validate_format(:nickname, local_nickname_regex())
|
|
||||||
|> validate_length(:bio, max: bio_limit)
|
|
||||||
|> validate_length(:name, max: name_limit)
|
|
||||||
|> validate_fields(remote?)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_as_admin_changeset(struct, params) do
|
def update_as_admin_changeset(struct, params) do
|
||||||
struct
|
struct
|
||||||
|> update_changeset(params)
|
|> update_changeset(params)
|
||||||
|
@ -606,7 +595,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|> confirmation_changeset(need_confirmation: need_confirmation?)
|
|> confirmation_changeset(need_confirmation: need_confirmation?)
|
||||||
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation, :emoji])
|
||||||
|> validate_required([:name, :nickname, :password, :password_confirmation])
|
|> validate_required([:name, :nickname, :password, :password_confirmation])
|
||||||
|> validate_confirmation(:password)
|
|> validate_confirmation(:password)
|
||||||
|> unique_constraint(:email)
|
|> unique_constraint(:email)
|
||||||
|
@ -1621,8 +1610,7 @@ defp create_service_actor(uri, nickname) do
|
||||||
|> set_cache()
|
|> set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
# AP style
|
def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
|
||||||
def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
|
|
||||||
key =
|
key =
|
||||||
public_key_pem
|
public_key_pem
|
||||||
|> :public_key.pem_decode()
|
|> :public_key.pem_decode()
|
||||||
|
@ -1632,7 +1620,7 @@ def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pe
|
||||||
{:ok, key}
|
{:ok, key}
|
||||||
end
|
end
|
||||||
|
|
||||||
def public_key(_), do: {:error, "not found key"}
|
def public_key(_), do: {:error, "key not found"}
|
||||||
|
|
||||||
def get_public_key_for_ap_id(ap_id) do
|
def get_public_key_for_ap_id(ap_id) do
|
||||||
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
|
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
|
||||||
|
@ -1643,17 +1631,6 @@ def get_public_key_for_ap_id(ap_id) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp blank?(""), do: nil
|
|
||||||
defp blank?(n), do: n
|
|
||||||
|
|
||||||
def insert_or_update_user(data) do
|
|
||||||
data
|
|
||||||
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|
|
||||||
|> remote_user_creation()
|
|
||||||
|> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
|
|
||||||
|> set_cache()
|
|
||||||
end
|
|
||||||
|
|
||||||
def ap_enabled?(%User{local: true}), do: true
|
def ap_enabled?(%User{local: true}), do: true
|
||||||
def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
|
def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
|
||||||
def ap_enabled?(_), do: false
|
def ap_enabled?(_), do: false
|
||||||
|
@ -1962,12 +1939,6 @@ def update_background(user, background) do
|
||||||
|> update_and_set_cache()
|
|> update_and_set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_source_data(user, source_data) do
|
|
||||||
user
|
|
||||||
|> cast(%{source_data: source_data}, [:source_data])
|
|
||||||
|> update_and_set_cache()
|
|
||||||
end
|
|
||||||
|
|
||||||
def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
|
def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
|
||||||
%{
|
%{
|
||||||
admin: is_admin,
|
admin: is_admin,
|
||||||
|
@ -1975,21 +1946,6 @@ def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
|
|
||||||
# For example: [{"name": "Pronoun", "value": "she/her"}, …]
|
|
||||||
def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
|
|
||||||
limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
|
|
||||||
|
|
||||||
attachment
|
|
||||||
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
|
||||||
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
|
||||||
|> Enum.take(limit)
|
|
||||||
end
|
|
||||||
|
|
||||||
def fields(%{fields: nil}), do: []
|
|
||||||
|
|
||||||
def fields(%{fields: fields}), do: fields
|
|
||||||
|
|
||||||
def validate_fields(changeset, remote? \\ false) do
|
def validate_fields(changeset, remote? \\ false) do
|
||||||
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
|
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
|
||||||
limit = Pleroma.Config.get([:instance, limit_name], 0)
|
limit = Pleroma.Config.get([:instance, limit_name], 0)
|
||||||
|
@ -2177,9 +2133,7 @@ def sanitize_html(%User{} = user) do
|
||||||
# - display name
|
# - display name
|
||||||
def sanitize_html(%User{} = user, filter) do
|
def sanitize_html(%User{} = user, filter) do
|
||||||
fields =
|
fields =
|
||||||
user
|
Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
|
||||||
|> User.fields()
|
|
||||||
|> Enum.map(fn %{"name" => name, "value" => value} ->
|
|
||||||
%{
|
%{
|
||||||
"name" => name,
|
"name" => name,
|
||||||
"value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
|
"value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
|
||||||
|
|
|
@ -130,17 +130,27 @@ def exists?(dictionary, rel_type, source, target, func) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc ":relationships option for StatusView / AccountView / NotificationView"
|
@doc ":relationships option for StatusView / AccountView / NotificationView"
|
||||||
def view_relationships_option(nil = _reading_user, _actors) do
|
def view_relationships_option(reading_user, actors, opts \\ [])
|
||||||
|
|
||||||
|
def view_relationships_option(nil = _reading_user, _actors, _opts) do
|
||||||
%{user_relationships: [], following_relationships: []}
|
%{user_relationships: [], following_relationships: []}
|
||||||
end
|
end
|
||||||
|
|
||||||
def view_relationships_option(%User{} = reading_user, actors) do
|
def view_relationships_option(%User{} = reading_user, actors, opts) do
|
||||||
|
{source_to_target_rel_types, target_to_source_rel_types} =
|
||||||
|
if opts[:source_mutes_only] do
|
||||||
|
# This option is used for rendering statuses (FE needs `muted` flag for each one anyways)
|
||||||
|
{[:mute], []}
|
||||||
|
else
|
||||||
|
{[:block, :mute, :notification_mute, :reblog_mute], [:block, :inverse_subscription]}
|
||||||
|
end
|
||||||
|
|
||||||
user_relationships =
|
user_relationships =
|
||||||
UserRelationship.dictionary(
|
UserRelationship.dictionary(
|
||||||
[reading_user],
|
[reading_user],
|
||||||
actors,
|
actors,
|
||||||
[:block, :mute, :notification_mute, :reblog_mute],
|
source_to_target_rel_types,
|
||||||
[:block, :inverse_subscription]
|
target_to_source_rel_types
|
||||||
)
|
)
|
||||||
|
|
||||||
following_relationships = FollowingRelationship.all_between_user_sets([reading_user], actors)
|
following_relationships = FollowingRelationship.all_between_user_sets([reading_user], actors)
|
||||||
|
|
|
@ -1427,19 +1427,44 @@ defp object_to_user_data(data) do
|
||||||
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
||||||
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
||||||
|
|
||||||
|
emojis =
|
||||||
|
data
|
||||||
|
|> Map.get("tag", [])
|
||||||
|
|> Enum.filter(fn
|
||||||
|
%{"type" => "Emoji"} -> true
|
||||||
|
_ -> false
|
||||||
|
end)
|
||||||
|
|> Enum.reduce(%{}, fn %{"icon" => %{"url" => url}, "name" => name}, acc ->
|
||||||
|
Map.put(acc, String.trim(name, ":"), url)
|
||||||
|
end)
|
||||||
|
|
||||||
locked = data["manuallyApprovesFollowers"] || false
|
locked = data["manuallyApprovesFollowers"] || false
|
||||||
data = Transmogrifier.maybe_fix_user_object(data)
|
data = Transmogrifier.maybe_fix_user_object(data)
|
||||||
discoverable = data["discoverable"] || false
|
discoverable = data["discoverable"] || false
|
||||||
invisible = data["invisible"] || false
|
invisible = data["invisible"] || false
|
||||||
actor_type = data["type"] || "Person"
|
actor_type = data["type"] || "Person"
|
||||||
|
|
||||||
|
public_key =
|
||||||
|
if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do
|
||||||
|
data["publicKey"]["publicKeyPem"]
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_inbox =
|
||||||
|
if is_map(data["endpoints"]) && is_binary(data["endpoints"]["sharedInbox"]) do
|
||||||
|
data["endpoints"]["sharedInbox"]
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
user_data = %{
|
user_data = %{
|
||||||
ap_id: data["id"],
|
ap_id: data["id"],
|
||||||
uri: get_actor_url(data["url"]),
|
uri: get_actor_url(data["url"]),
|
||||||
ap_enabled: true,
|
ap_enabled: true,
|
||||||
source_data: data,
|
|
||||||
banner: banner,
|
banner: banner,
|
||||||
fields: fields,
|
fields: fields,
|
||||||
|
emoji: emojis,
|
||||||
locked: locked,
|
locked: locked,
|
||||||
discoverable: discoverable,
|
discoverable: discoverable,
|
||||||
invisible: invisible,
|
invisible: invisible,
|
||||||
|
@ -1449,7 +1474,10 @@ defp object_to_user_data(data) do
|
||||||
following_address: data["following"],
|
following_address: data["following"],
|
||||||
bio: data["summary"],
|
bio: data["summary"],
|
||||||
actor_type: actor_type,
|
actor_type: actor_type,
|
||||||
also_known_as: Map.get(data, "alsoKnownAs", [])
|
also_known_as: Map.get(data, "alsoKnownAs", []),
|
||||||
|
public_key: public_key,
|
||||||
|
inbox: data["inbox"],
|
||||||
|
shared_inbox: shared_inbox
|
||||||
}
|
}
|
||||||
|
|
||||||
# nickname can be nil because of virtual actors
|
# nickname can be nil because of virtual actors
|
||||||
|
@ -1551,11 +1579,22 @@ def fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_user_from_ap_id(ap_id) do
|
def make_user_from_ap_id(ap_id) do
|
||||||
if _user = User.get_cached_by_ap_id(ap_id) do
|
user = User.get_cached_by_ap_id(ap_id)
|
||||||
|
|
||||||
|
if user && !User.ap_enabled?(user) do
|
||||||
Transmogrifier.upgrade_user_from_ap_id(ap_id)
|
Transmogrifier.upgrade_user_from_ap_id(ap_id)
|
||||||
else
|
else
|
||||||
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
|
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||||
User.insert_or_update_user(data)
|
if user do
|
||||||
|
user
|
||||||
|
|> User.remote_user_changeset(data)
|
||||||
|
|> User.update_and_set_cache()
|
||||||
|
else
|
||||||
|
data
|
||||||
|
|> User.remote_user_changeset()
|
||||||
|
|> Repo.insert()
|
||||||
|
|> User.set_cache()
|
||||||
|
end
|
||||||
else
|
else
|
||||||
e -> {:error, e}
|
e -> {:error, e}
|
||||||
end
|
end
|
||||||
|
|
|
@ -35,6 +35,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
|
||||||
field(:like_count, :integer, default: 0)
|
field(:like_count, :integer, default: 0)
|
||||||
field(:announcement_count, :integer, default: 0)
|
field(:announcement_count, :integer, default: 0)
|
||||||
field(:inRepyTo, :string)
|
field(:inRepyTo, :string)
|
||||||
|
field(:uri, Types.Uri)
|
||||||
|
|
||||||
field(:likes, {:array, :string}, default: [])
|
field(:likes, {:array, :string}, default: [])
|
||||||
field(:announcements, {:array, :string}, default: [])
|
field(:announcements, {:array, :string}, default: [])
|
||||||
|
|
|
@ -15,15 +15,9 @@ def cast(object) when is_binary(object) do
|
||||||
|
|
||||||
def cast(%{"id" => object}), do: cast(object)
|
def cast(%{"id" => object}), do: cast(object)
|
||||||
|
|
||||||
def cast(_) do
|
def cast(_), do: :error
|
||||||
:error
|
|
||||||
end
|
|
||||||
|
|
||||||
def dump(data) do
|
def dump(data), do: {:ok, data}
|
||||||
{:ok, data}
|
|
||||||
end
|
|
||||||
|
|
||||||
def load(data) do
|
def load(data), do: {:ok, data}
|
||||||
{:ok, data}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
20
lib/pleroma/web/activity_pub/object_validators/types/uri.ex
Normal file
20
lib/pleroma/web/activity_pub/object_validators/types/uri.ex
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.Uri do
|
||||||
|
use Ecto.Type
|
||||||
|
|
||||||
|
def type, do: :string
|
||||||
|
|
||||||
|
def cast(uri) when is_binary(uri) do
|
||||||
|
case URI.parse(uri) do
|
||||||
|
%URI{host: nil} -> :error
|
||||||
|
%URI{host: ""} -> :error
|
||||||
|
%URI{scheme: scheme} when scheme in ["https", "http"] -> {:ok, uri}
|
||||||
|
_ -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast(_), do: :error
|
||||||
|
|
||||||
|
def dump(data), do: {:ok, data}
|
||||||
|
|
||||||
|
def load(data), do: {:ok, data}
|
||||||
|
end
|
|
@ -141,8 +141,8 @@ defp get_cc_ap_ids(ap_id, recipients) do
|
||||||
|> Enum.map(& &1.ap_id)
|
|> Enum.map(& &1.ap_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_use_sharedinbox(%User{source_data: data}),
|
defp maybe_use_sharedinbox(%User{shared_inbox: nil, inbox: inbox}), do: inbox
|
||||||
do: (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
|
defp maybe_use_sharedinbox(%User{shared_inbox: shared_inbox}), do: shared_inbox
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Determine a user inbox to use based on heuristics. These heuristics
|
Determine a user inbox to use based on heuristics. These heuristics
|
||||||
|
@ -157,7 +157,7 @@ defp maybe_use_sharedinbox(%User{source_data: data}),
|
||||||
"""
|
"""
|
||||||
def determine_inbox(
|
def determine_inbox(
|
||||||
%Activity{data: activity_data},
|
%Activity{data: activity_data},
|
||||||
%User{source_data: data} = user
|
%User{inbox: inbox} = user
|
||||||
) do
|
) do
|
||||||
to = activity_data["to"] || []
|
to = activity_data["to"] || []
|
||||||
cc = activity_data["cc"] || []
|
cc = activity_data["cc"] || []
|
||||||
|
@ -174,7 +174,7 @@ def determine_inbox(
|
||||||
maybe_use_sharedinbox(user)
|
maybe_use_sharedinbox(user)
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
data["inbox"]
|
inbox
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -192,14 +192,13 @@ def publish(%User{} = actor, %{data: %{"bcc" => bcc}} = activity)
|
||||||
inboxes =
|
inboxes =
|
||||||
recipients
|
recipients
|
||||||
|> Enum.filter(&User.ap_enabled?/1)
|
|> Enum.filter(&User.ap_enabled?/1)
|
||||||
|> Enum.map(fn %{source_data: data} -> data["inbox"] end)
|
|> Enum.map(fn actor -> actor.inbox end)
|
||||||
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|
||||||
|> Instances.filter_reachable()
|
|> Instances.filter_reachable()
|
||||||
|
|
||||||
Repo.checkout(fn ->
|
Repo.checkout(fn ->
|
||||||
Enum.each(inboxes, fn {inbox, unreachable_since} ->
|
Enum.each(inboxes, fn {inbox, unreachable_since} ->
|
||||||
%User{ap_id: ap_id} =
|
%User{ap_id: ap_id} = Enum.find(recipients, fn actor -> actor.inbox == inbox end)
|
||||||
Enum.find(recipients, fn %{source_data: data} -> data["inbox"] == inbox end)
|
|
||||||
|
|
||||||
# Get all the recipients on the same host and add them to cc. Otherwise, a remote
|
# Get all the recipients on the same host and add them to cc. Otherwise, a remote
|
||||||
# instance would only accept a first message for the first recipient and ignore the rest.
|
# instance would only accept a first message for the first recipient and ignore the rest.
|
||||||
|
|
|
@ -17,7 +17,9 @@ def handle(object, meta \\ [])
|
||||||
def handle(%{data: %{"type" => "Like"}} = object, meta) do
|
def handle(%{data: %{"type" => "Like"}} = object, meta) do
|
||||||
liked_object = Object.get_by_ap_id(object.data["object"])
|
liked_object = Object.get_by_ap_id(object.data["object"])
|
||||||
Utils.add_like_to_object(object, liked_object)
|
Utils.add_like_to_object(object, liked_object)
|
||||||
|
|
||||||
Notification.create_notifications(object)
|
Notification.create_notifications(object)
|
||||||
|
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -711,7 +711,7 @@ def handle_incoming(
|
||||||
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
|
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
|
||||||
|
|
||||||
actor
|
actor
|
||||||
|> User.upgrade_changeset(new_user_data, true)
|
|> User.remote_user_changeset(new_user_data)
|
||||||
|> User.update_and_set_cache()
|
|> User.update_and_set_cache()
|
||||||
|
|
||||||
ActivityPub.update(%{
|
ActivityPub.update(%{
|
||||||
|
@ -1160,7 +1160,7 @@ defp build_mention_tag(%{ap_id: ap_id, nickname: nickname} = _) do
|
||||||
|
|
||||||
def take_emoji_tags(%User{emoji: emoji}) do
|
def take_emoji_tags(%User{emoji: emoji}) do
|
||||||
emoji
|
emoji
|
||||||
|> Enum.flat_map(&Map.to_list/1)
|
|> Map.to_list()
|
||||||
|> Enum.map(&build_emoji_tag/1)
|
|> Enum.map(&build_emoji_tag/1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1254,12 +1254,8 @@ def perform(:user_upgrade, user) do
|
||||||
def upgrade_user_from_ap_id(ap_id) do
|
def upgrade_user_from_ap_id(ap_id) do
|
||||||
with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
|
with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
|
||||||
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
|
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
|
||||||
already_ap <- User.ap_enabled?(user),
|
{:ok, user} <- update_user(user, data) do
|
||||||
{:ok, user} <- upgrade_user(user, data) do
|
|
||||||
if not already_ap do
|
|
||||||
TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
|
TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
|
||||||
end
|
|
||||||
|
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
%User{} = user -> {:ok, user}
|
%User{} = user -> {:ok, user}
|
||||||
|
@ -1267,9 +1263,9 @@ def upgrade_user_from_ap_id(ap_id) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp upgrade_user(user, data) do
|
defp update_user(user, data) do
|
||||||
user
|
user
|
||||||
|> User.upgrade_changeset(data, true)
|
|> User.remote_user_changeset(data)
|
||||||
|> User.update_and_set_cache()
|
|> User.update_and_set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -79,10 +79,7 @@ def render("user.json", %{user: user}) do
|
||||||
|
|
||||||
emoji_tags = Transmogrifier.take_emoji_tags(user)
|
emoji_tags = Transmogrifier.take_emoji_tags(user)
|
||||||
|
|
||||||
fields =
|
fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue"))
|
||||||
user
|
|
||||||
|> User.fields()
|
|
||||||
|> Enum.map(&Map.put(&1, "type", "PropertyValue"))
|
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"id" => user.ap_id,
|
"id" => user.ap_id,
|
||||||
|
@ -103,7 +100,7 @@ def render("user.json", %{user: user}) do
|
||||||
},
|
},
|
||||||
"endpoints" => endpoints,
|
"endpoints" => endpoints,
|
||||||
"attachment" => fields,
|
"attachment" => fields,
|
||||||
"tag" => (user.source_data["tag"] || []) ++ emoji_tags,
|
"tag" => emoji_tags,
|
||||||
"discoverable" => user.discoverable
|
"discoverable" => user.discoverable
|
||||||
}
|
}
|
||||||
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|
||||||
|
|
|
@ -27,7 +27,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
alias Pleroma.Web.AdminAPI.Search
|
alias Pleroma.Web.AdminAPI.Search
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.Endpoint
|
alias Pleroma.Web.Endpoint
|
||||||
|
alias Pleroma.Web.MastodonAPI.AppView
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
alias Pleroma.Web.OAuth.App
|
||||||
alias Pleroma.Web.Router
|
alias Pleroma.Web.Router
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
@ -258,7 +260,7 @@ def list_instance_statuses(conn, %{"instance" => instance} = params) do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(Pleroma.Web.AdminAPI.StatusView)
|
|> put_view(Pleroma.Web.AdminAPI.StatusView)
|
||||||
|> render("index.json", %{activities: activities, as: :activity})
|
|> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_user_statuses(conn, %{"nickname" => nickname} = params) do
|
def list_user_statuses(conn, %{"nickname" => nickname} = params) do
|
||||||
|
@ -277,7 +279,7 @@ def list_user_statuses(conn, %{"nickname" => nickname} = params) do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> render("index.json", %{activities: activities, as: :activity})
|
|> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
|
||||||
else
|
else
|
||||||
_ -> {:error, :not_found}
|
_ -> {:error, :not_found}
|
||||||
end
|
end
|
||||||
|
@ -812,7 +814,7 @@ def list_statuses(%{assigns: %{user: _admin}} = conn, params) do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(Pleroma.Web.AdminAPI.StatusView)
|
|> put_view(Pleroma.Web.AdminAPI.StatusView)
|
||||||
|> render("index.json", %{activities: activities, as: :activity})
|
|> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
|
||||||
end
|
end
|
||||||
|
|
||||||
def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
|
def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
|
||||||
|
@ -914,16 +916,7 @@ def config_show(conn, _params) do
|
||||||
end)
|
end)
|
||||||
|> List.flatten()
|
|> List.flatten()
|
||||||
|
|
||||||
response = %{configs: merged}
|
json(conn, %{configs: merged, need_reboot: Restarter.Pleroma.need_reboot?()})
|
||||||
|
|
||||||
response =
|
|
||||||
if Restarter.Pleroma.need_reboot?() do
|
|
||||||
Map.put(response, :need_reboot, true)
|
|
||||||
else
|
|
||||||
response
|
|
||||||
end
|
|
||||||
|
|
||||||
json(conn, response)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -950,28 +943,22 @@ def config_update(conn, %{"configs" => configs}) do
|
||||||
|
|
||||||
Config.TransferTask.load_and_update_env(deleted, false)
|
Config.TransferTask.load_and_update_env(deleted, false)
|
||||||
|
|
||||||
need_reboot? =
|
if !Restarter.Pleroma.need_reboot?() do
|
||||||
Restarter.Pleroma.need_reboot?() ||
|
changed_reboot_settings? =
|
||||||
Enum.any?(updated, fn config ->
|
(updated ++ deleted)
|
||||||
|
|> Enum.any?(fn config ->
|
||||||
group = ConfigDB.from_string(config.group)
|
group = ConfigDB.from_string(config.group)
|
||||||
key = ConfigDB.from_string(config.key)
|
key = ConfigDB.from_string(config.key)
|
||||||
value = ConfigDB.from_binary(config.value)
|
value = ConfigDB.from_binary(config.value)
|
||||||
Config.TransferTask.pleroma_need_restart?(group, key, value)
|
Config.TransferTask.pleroma_need_restart?(group, key, value)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
response = %{configs: updated}
|
if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot()
|
||||||
|
|
||||||
response =
|
|
||||||
if need_reboot? do
|
|
||||||
Restarter.Pleroma.need_reboot()
|
|
||||||
Map.put(response, :need_reboot, need_reboot?)
|
|
||||||
else
|
|
||||||
response
|
|
||||||
end
|
end
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(ConfigView)
|
|> put_view(ConfigView)
|
||||||
|> render("index.json", response)
|
|> render("index.json", %{configs: updated, need_reboot: Restarter.Pleroma.need_reboot?()})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -983,6 +970,10 @@ def restart(conn, _params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def need_reboot(conn, _params) do
|
||||||
|
json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
|
||||||
|
end
|
||||||
|
|
||||||
defp configurable_from_database(conn) do
|
defp configurable_from_database(conn) do
|
||||||
if Config.get(:configurable_from_database) do
|
if Config.get(:configurable_from_database) do
|
||||||
:ok
|
:ok
|
||||||
|
@ -1028,6 +1019,83 @@ def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" =
|
||||||
conn |> json("")
|
conn |> json("")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def oauth_app_create(conn, params) do
|
||||||
|
params =
|
||||||
|
if params["name"] do
|
||||||
|
Map.put(params, "client_name", params["name"])
|
||||||
|
else
|
||||||
|
params
|
||||||
|
end
|
||||||
|
|
||||||
|
result =
|
||||||
|
case App.create(params) do
|
||||||
|
{:ok, app} ->
|
||||||
|
AppView.render("show.json", %{app: app, admin: true})
|
||||||
|
|
||||||
|
{:error, changeset} ->
|
||||||
|
App.errors(changeset)
|
||||||
|
end
|
||||||
|
|
||||||
|
json(conn, result)
|
||||||
|
end
|
||||||
|
|
||||||
|
def oauth_app_update(conn, params) do
|
||||||
|
params =
|
||||||
|
if params["name"] do
|
||||||
|
Map.put(params, "client_name", params["name"])
|
||||||
|
else
|
||||||
|
params
|
||||||
|
end
|
||||||
|
|
||||||
|
with {:ok, app} <- App.update(params) do
|
||||||
|
json(conn, AppView.render("show.json", %{app: app, admin: true}))
|
||||||
|
else
|
||||||
|
{:error, changeset} ->
|
||||||
|
json(conn, App.errors(changeset))
|
||||||
|
|
||||||
|
nil ->
|
||||||
|
json_response(conn, :bad_request, "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def oauth_app_list(conn, params) do
|
||||||
|
{page, page_size} = page_params(params)
|
||||||
|
|
||||||
|
search_params = %{
|
||||||
|
client_name: params["name"],
|
||||||
|
client_id: params["client_id"],
|
||||||
|
page: page,
|
||||||
|
page_size: page_size
|
||||||
|
}
|
||||||
|
|
||||||
|
search_params =
|
||||||
|
if Map.has_key?(params, "trusted") do
|
||||||
|
Map.put(search_params, :trusted, params["trusted"])
|
||||||
|
else
|
||||||
|
search_params
|
||||||
|
end
|
||||||
|
|
||||||
|
with {:ok, apps, count} <- App.search(search_params) do
|
||||||
|
json(
|
||||||
|
conn,
|
||||||
|
AppView.render("index.json",
|
||||||
|
apps: apps,
|
||||||
|
count: count,
|
||||||
|
page_size: page_size,
|
||||||
|
admin: true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def oauth_app_delete(conn, params) do
|
||||||
|
with {:ok, _app} <- App.destroy(params["id"]) do
|
||||||
|
json_response(conn, :no_content, "")
|
||||||
|
else
|
||||||
|
_ -> json_response(conn, :bad_request, "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def stats(conn, _) do
|
def stats(conn, _) do
|
||||||
count = Stats.get_status_visibility_count()
|
count = Stats.get_status_visibility_count()
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,12 @@ def render("show.json", %{report: report, user: user, account: account, statuses
|
||||||
actor: merge_account_views(user),
|
actor: merge_account_views(user),
|
||||||
content: content,
|
content: content,
|
||||||
created_at: created_at,
|
created_at: created_at,
|
||||||
statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}),
|
statuses:
|
||||||
|
StatusView.render("index.json", %{
|
||||||
|
activities: statuses,
|
||||||
|
as: :activity,
|
||||||
|
skip_relationships: false
|
||||||
|
}),
|
||||||
state: report.data["state"],
|
state: report.data["state"],
|
||||||
notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes})
|
notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes})
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ def spec do
|
||||||
password: %OpenApiSpex.OAuthFlow{
|
password: %OpenApiSpex.OAuthFlow{
|
||||||
authorizationUrl: "/oauth/authorize",
|
authorizationUrl: "/oauth/authorize",
|
||||||
tokenUrl: "/oauth/token",
|
tokenUrl: "/oauth/token",
|
||||||
scopes: %{"read" => "read"}
|
scopes: %{"read" => "read", "write" => "write", "follow" => "follow"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.ApiSpec.Helpers do
|
defmodule Pleroma.Web.ApiSpec.Helpers do
|
||||||
def request_body(description, schema_ref, opts \\ []) do
|
def request_body(description, schema_ref, opts \\ []) do
|
||||||
media_types = ["application/json", "multipart/form-data"]
|
media_types = ["application/json", "multipart/form-data", "application/x-www-form-urlencoded"]
|
||||||
|
|
||||||
content =
|
content =
|
||||||
media_types
|
media_types
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
# 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.DomainBlockOperation do
|
||||||
|
alias OpenApiSpex.Operation
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
alias Pleroma.Web.ApiSpec.Helpers
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.DomainBlockRequest
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse
|
||||||
|
|
||||||
|
def open_api_operation(action) do
|
||||||
|
operation = String.to_existing_atom("#{action}_operation")
|
||||||
|
apply(__MODULE__, operation, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def index_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["domain_blocks"],
|
||||||
|
summary: "Fetch domain blocks",
|
||||||
|
description: "View domains the user has blocked.",
|
||||||
|
security: [%{"oAuth" => ["follow", "read:blocks"]}],
|
||||||
|
operationId: "DomainBlockController.index",
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Domain blocks", "application/json", DomainBlocksResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["domain_blocks"],
|
||||||
|
summary: "Block a domain",
|
||||||
|
description: """
|
||||||
|
Block a domain to:
|
||||||
|
|
||||||
|
- hide all public posts from it
|
||||||
|
- hide all notifications from it
|
||||||
|
- remove all followers from it
|
||||||
|
- prevent following new users from it (but does not remove existing follows)
|
||||||
|
""",
|
||||||
|
operationId: "DomainBlockController.create",
|
||||||
|
requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true),
|
||||||
|
security: [%{"oAuth" => ["follow", "write:blocks"]}],
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["domain_blocks"],
|
||||||
|
summary: "Unblock a domain",
|
||||||
|
description: "Remove a domain block, if it exists in the user's array of blocked domains.",
|
||||||
|
operationId: "DomainBlockController.delete",
|
||||||
|
requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true),
|
||||||
|
security: [%{"oAuth" => ["follow", "write:blocks"]}],
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
20
lib/pleroma/web/api_spec/schemas/domain_block_request.ex
Normal file
20
lib/pleroma/web/api_spec/schemas/domain_block_request.ex
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# 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.Schemas.DomainBlockRequest do
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "DomainBlockRequest",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
domain: %Schema{type: :string}
|
||||||
|
},
|
||||||
|
required: [:domain],
|
||||||
|
example: %{
|
||||||
|
"domain" => "facebook.com"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
16
lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex
Normal file
16
lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# 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.Schemas.DomainBlocksResponse do
|
||||||
|
require OpenApiSpex
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "DomainBlocksResponse",
|
||||||
|
description: "Response schema for domain blocks",
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{type: :string},
|
||||||
|
example: ["google.com", "facebook.com"]
|
||||||
|
})
|
||||||
|
end
|
|
@ -187,7 +187,7 @@ defp object(draft) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp preview?(draft) do
|
defp preview?(draft) do
|
||||||
preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"]) || false
|
preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"])
|
||||||
%__MODULE__{draft | preview?: preview?}
|
%__MODULE__{draft | preview?: preview?}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -332,26 +332,6 @@ defp maybe_create_activity_expiration({:ok, activity}, %NaiveDateTime{} = expire
|
||||||
|
|
||||||
defp maybe_create_activity_expiration(result, _), do: result
|
defp maybe_create_activity_expiration(result, _), do: result
|
||||||
|
|
||||||
# Updates the emojis for a user based on their profile
|
|
||||||
def update(user) do
|
|
||||||
emoji = emoji_from_profile(user)
|
|
||||||
source_data = Map.put(user.source_data, "tag", emoji)
|
|
||||||
|
|
||||||
user =
|
|
||||||
case User.update_source_data(user, source_data) do
|
|
||||||
{:ok, user} -> user
|
|
||||||
_ -> user
|
|
||||||
end
|
|
||||||
|
|
||||||
ActivityPub.update(%{
|
|
||||||
local: true,
|
|
||||||
to: [Pleroma.Constants.as_public(), user.follower_address],
|
|
||||||
cc: [],
|
|
||||||
actor: user.ap_id,
|
|
||||||
object: Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
|
def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
|
||||||
with %Activity{
|
with %Activity{
|
||||||
actor: ^user_ap_id,
|
actor: ^user_ap_id,
|
||||||
|
|
|
@ -10,7 +10,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
alias Pleroma.Emoji
|
|
||||||
alias Pleroma.Formatter
|
alias Pleroma.Formatter
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Plugs.AuthenticationPlug
|
alias Pleroma.Plugs.AuthenticationPlug
|
||||||
|
@ -18,7 +17,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.Endpoint
|
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
@ -175,7 +173,7 @@ def make_poll_data(%{"poll" => %{"options" => options, "expires_in" => expires_i
|
||||||
"replies" => %{"type" => "Collection", "totalItems" => 0}
|
"replies" => %{"type" => "Collection", "totalItems" => 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
{note, Map.merge(emoji, Emoji.Formatter.get_emoji_map(option))}
|
{note, Map.merge(emoji, Pleroma.Emoji.Formatter.get_emoji_map(option))}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
end_time =
|
end_time =
|
||||||
|
@ -431,19 +429,6 @@ def confirm_current_password(user, password) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def emoji_from_profile(%User{bio: bio, name: name}) do
|
|
||||||
[bio, name]
|
|
||||||
|> Enum.map(&Emoji.Formatter.get_emoji/1)
|
|
||||||
|> Enum.concat()
|
|
||||||
|> Enum.map(fn {shortcode, %Emoji{file: path}} ->
|
|
||||||
%{
|
|
||||||
"type" => "Emoji",
|
|
||||||
"icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{path}"},
|
|
||||||
"name" => ":#{shortcode}:"
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def maybe_notify_to_recipients(
|
def maybe_notify_to_recipients(
|
||||||
recipients,
|
recipients,
|
||||||
%Activity{data: %{"to" => to, "type" => _type}} = _activity
|
%Activity{data: %{"to" => to, "type" => _type}} = _activity
|
||||||
|
|
|
@ -5,10 +5,18 @@
|
||||||
defmodule Pleroma.Web.ControllerHelper do
|
defmodule Pleroma.Web.ControllerHelper do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
# As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
|
alias Pleroma.Config
|
||||||
|
|
||||||
|
# As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
|
||||||
@falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"]
|
@falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"]
|
||||||
def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil
|
|
||||||
def truthy_param?(value), do: value not in @falsy_param_values
|
def explicitly_falsy_param?(value), do: value in @falsy_param_values
|
||||||
|
|
||||||
|
# Note: `nil` and `""` are considered falsy values in Pleroma
|
||||||
|
def falsy_param?(value),
|
||||||
|
do: explicitly_falsy_param?(value) or value in [nil, ""]
|
||||||
|
|
||||||
|
def truthy_param?(value), do: not falsy_param?(value)
|
||||||
|
|
||||||
def json_response(conn, status, json) do
|
def json_response(conn, status, json) do
|
||||||
conn
|
conn
|
||||||
|
@ -96,4 +104,14 @@ def try_render(conn, _, _) do
|
||||||
def put_if_exist(map, _key, nil), do: map
|
def put_if_exist(map, _key, nil), do: map
|
||||||
|
|
||||||
def put_if_exist(map, key, value), do: Map.put(map, key, value)
|
def put_if_exist(map, key, value), do: Map.put(map, key, value)
|
||||||
|
|
||||||
|
@doc "Whether to skip rendering `[:account][:pleroma][:relationship]`for statuses/notifications"
|
||||||
|
def skip_relationships?(params) do
|
||||||
|
if Config.get([:extensions, :output_relationships_in_statuses_by_default]) do
|
||||||
|
false
|
||||||
|
else
|
||||||
|
# BREAKING: older PleromaFE versions do not send this param but _do_ expect relationships.
|
||||||
|
not truthy_param?(params["with_relationships"])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,7 +23,7 @@ def pub_date(date) when is_binary(date) do
|
||||||
def pub_date(%DateTime{} = date), do: Timex.format!(date, "{RFC822}")
|
def pub_date(%DateTime{} = date), do: Timex.format!(date, "{RFC822}")
|
||||||
|
|
||||||
def prepare_activity(activity, opts \\ []) do
|
def prepare_activity(activity, opts \\ []) do
|
||||||
object = activity_object(activity)
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
actor =
|
actor =
|
||||||
if opts[:actor] do
|
if opts[:actor] do
|
||||||
|
@ -33,7 +33,6 @@ def prepare_activity(activity, opts \\ []) do
|
||||||
%{
|
%{
|
||||||
activity: activity,
|
activity: activity,
|
||||||
data: Map.get(object, :data),
|
data: Map.get(object, :data),
|
||||||
object: object,
|
|
||||||
actor: actor
|
actor: actor
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -68,9 +67,7 @@ def logo(user) do
|
||||||
|
|
||||||
def last_activity(activities), do: List.last(activities)
|
def last_activity(activities), do: List.last(activities)
|
||||||
|
|
||||||
def activity_object(activity), do: Object.normalize(activity)
|
def activity_title(%{"content" => content}, opts \\ %{}) do
|
||||||
|
|
||||||
def activity_title(%{data: %{"content" => content}}, opts \\ %{}) do
|
|
||||||
content
|
content
|
||||||
|> Pleroma.Web.Metadata.Utils.scrub_html()
|
|> Pleroma.Web.Metadata.Utils.scrub_html()
|
||||||
|> Pleroma.Emoji.Formatter.demojify()
|
|> Pleroma.Emoji.Formatter.demojify()
|
||||||
|
@ -78,7 +75,7 @@ def activity_title(%{data: %{"content" => content}}, opts \\ %{}) do
|
||||||
|> escape()
|
|> escape()
|
||||||
end
|
end
|
||||||
|
|
||||||
def activity_content(%{data: %{"content" => content}}) do
|
def activity_content(%{"content" => content}) do
|
||||||
content
|
content
|
||||||
|> String.replace(~r/[\n\r]/, "")
|
|> String.replace(~r/[\n\r]/, "")
|
||||||
|> escape()
|
|> escape()
|
||||||
|
|
|
@ -17,7 +17,7 @@ defmodule Pleroma.Web.MastoFEController do
|
||||||
when action == :index
|
when action == :index
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action != :index)
|
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action not in [:index, :manifest])
|
||||||
|
|
||||||
@doc "GET /web/*path"
|
@doc "GET /web/*path"
|
||||||
def index(%{assigns: %{user: user, token: token}} = conn, _params)
|
def index(%{assigns: %{user: user, token: token}} = conn, _params)
|
||||||
|
|
|
@ -6,7 +6,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
import Pleroma.Web.ControllerHelper,
|
import Pleroma.Web.ControllerHelper,
|
||||||
only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3]
|
only: [
|
||||||
|
add_link_headers: 2,
|
||||||
|
truthy_param?: 1,
|
||||||
|
assign_account_by_id: 2,
|
||||||
|
json_response: 3,
|
||||||
|
skip_relationships?: 1
|
||||||
|
]
|
||||||
|
|
||||||
alias Pleroma.Plugs.OAuthScopesPlug
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
alias Pleroma.Plugs.RateLimiter
|
alias Pleroma.Plugs.RateLimiter
|
||||||
|
@ -15,10 +21,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.MastodonAPI.ListView
|
alias Pleroma.Web.MastodonAPI.ListView
|
||||||
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
||||||
|
alias Pleroma.Web.MastodonAPI.MastodonAPIController
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
alias Pleroma.Web.OAuth.Token
|
alias Pleroma.Web.OAuth.Token
|
||||||
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||||
|
|
||||||
|
plug(:skip_plug, OAuthScopesPlug when action == :identity_proofs)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
|
%{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
|
||||||
|
@ -95,6 +104,7 @@ def create(
|
||||||
|> Map.put("fullname", params["fullname"] || nickname)
|
|> Map.put("fullname", params["fullname"] || nickname)
|
||||||
|> Map.put("bio", params["bio"] || "")
|
|> Map.put("bio", params["bio"] || "")
|
||||||
|> Map.put("confirm", params["password"])
|
|> Map.put("confirm", params["password"])
|
||||||
|
|> Map.put("trusted_app", app.trusted)
|
||||||
|
|
||||||
with :ok <- validate_email_param(params),
|
with :ok <- validate_email_param(params),
|
||||||
{:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
|
{:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
|
||||||
|
@ -140,9 +150,7 @@ def verify_credentials(%{assigns: %{user: user}} = conn, _) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "PATCH /api/v1/accounts/update_credentials"
|
@doc "PATCH /api/v1/accounts/update_credentials"
|
||||||
def update_credentials(%{assigns: %{user: original_user}} = conn, params) do
|
def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
||||||
user = original_user
|
|
||||||
|
|
||||||
user_params =
|
user_params =
|
||||||
[
|
[
|
||||||
:no_rich_text,
|
:no_rich_text,
|
||||||
|
@ -178,8 +186,6 @@ def update_credentials(%{assigns: %{user: original_user}} = conn, params) do
|
||||||
changeset = User.update_changeset(user, user_params)
|
changeset = User.update_changeset(user, user_params)
|
||||||
|
|
||||||
with {:ok, user} <- User.update_and_set_cache(changeset) do
|
with {:ok, user} <- User.update_and_set_cache(changeset) do
|
||||||
if original_user != user, do: CommonAPI.update(user)
|
|
||||||
|
|
||||||
render(conn, "show.json", user: user, for: user, with_pleroma_settings: true)
|
render(conn, "show.json", user: user, for: user, with_pleroma_settings: true)
|
||||||
else
|
else
|
||||||
_e -> render_error(conn, :forbidden, "Invalid request")
|
_e -> render_error(conn, :forbidden, "Invalid request")
|
||||||
|
@ -237,7 +243,12 @@ def statuses(%{assigns: %{user: reading_user}} = conn, params) do
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(activities)
|
|> add_link_headers(activities)
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> render("index.json", activities: activities, for: reading_user, as: :activity)
|
|> render("index.json",
|
||||||
|
activities: activities,
|
||||||
|
for: reading_user,
|
||||||
|
as: :activity,
|
||||||
|
skip_relationships: skip_relationships?(params)
|
||||||
|
)
|
||||||
else
|
else
|
||||||
_e -> render_error(conn, :not_found, "Can't find user")
|
_e -> render_error(conn, :not_found, "Can't find user")
|
||||||
end
|
end
|
||||||
|
@ -369,6 +380,8 @@ def blocks(%{assigns: %{user: user}} = conn, _) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "GET /api/v1/endorsements"
|
@doc "GET /api/v1/endorsements"
|
||||||
def endorsements(conn, params),
|
def endorsements(conn, params), do: MastodonAPIController.empty_array(conn, params)
|
||||||
do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params)
|
|
||||||
|
@doc "GET /api/v1/identity_proofs"
|
||||||
|
def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params)
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,6 +8,9 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
|
||||||
alias Pleroma.Plugs.OAuthScopesPlug
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
|
plug(OpenApiSpex.Plug.CastAndValidate)
|
||||||
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["follow", "read:blocks"]} when action == :index
|
%{scopes: ["follow", "read:blocks"]} when action == :index
|
||||||
|
@ -26,13 +29,13 @@ def index(%{assigns: %{user: user}} = conn, _) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "POST /api/v1/domain_blocks"
|
@doc "POST /api/v1/domain_blocks"
|
||||||
def create(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
|
def create(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do
|
||||||
User.block_domain(blocker, domain)
|
User.block_domain(blocker, domain)
|
||||||
json(conn, %{})
|
json(conn, %{})
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "DELETE /api/v1/domain_blocks"
|
@doc "DELETE /api/v1/domain_blocks"
|
||||||
def delete(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
|
def delete(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do
|
||||||
User.unblock_domain(blocker, domain)
|
User.unblock_domain(blocker, domain)
|
||||||
json(conn, %{})
|
json(conn, %{})
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,21 +3,31 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
|
@moduledoc """
|
||||||
|
Contains stubs for unimplemented Mastodon API endpoints.
|
||||||
|
|
||||||
|
Note: instead of routing directly to this controller's action,
|
||||||
|
it's preferable to define an action in relevant (non-generic) controller,
|
||||||
|
set up OAuth rules for it and call this controller's function from it.
|
||||||
|
"""
|
||||||
|
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
plug(:skip_plug, Pleroma.Plugs.OAuthScopesPlug when action in [:empty_array, :empty_object])
|
||||||
|
|
||||||
|
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
|
||||||
|
|
||||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||||
|
|
||||||
# Stubs for unimplemented mastodon api
|
|
||||||
#
|
|
||||||
def empty_array(conn, _) do
|
def empty_array(conn, _) do
|
||||||
Logger.debug("Unimplemented, returning an empty array")
|
Logger.debug("Unimplemented, returning an empty array (list)")
|
||||||
json(conn, [])
|
json(conn, [])
|
||||||
end
|
end
|
||||||
|
|
||||||
def empty_object(conn, _) do
|
def empty_object(conn, _) do
|
||||||
Logger.debug("Unimplemented, returning an empty object")
|
Logger.debug("Unimplemented, returning an empty object (map)")
|
||||||
json(conn, %{})
|
json(conn, %{})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
defmodule Pleroma.Web.MastodonAPI.NotificationController do
|
defmodule Pleroma.Web.MastodonAPI.NotificationController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
|
||||||
|
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Plugs.OAuthScopesPlug
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
|
@ -45,7 +45,11 @@ def index(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(notifications)
|
|> add_link_headers(notifications)
|
||||||
|> render("index.json", notifications: notifications, for: user)
|
|> render("index.json",
|
||||||
|
notifications: notifications,
|
||||||
|
for: user,
|
||||||
|
skip_relationships: skip_relationships?(params)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET /api/v1/notifications/:id
|
# GET /api/v1/notifications/:id
|
||||||
|
|
|
@ -5,13 +5,14 @@
|
||||||
defmodule Pleroma.Web.MastodonAPI.SearchController do
|
defmodule Pleroma.Web.MastodonAPI.SearchController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 2, skip_relationships?: 1]
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Plugs.OAuthScopesPlug
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
alias Pleroma.Plugs.RateLimiter
|
alias Pleroma.Plugs.RateLimiter
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
alias Pleroma.Web.ControllerHelper
|
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
|
@ -66,10 +67,11 @@ defp do_search(version, %{assigns: %{user: user}} = conn, %{"q" => query} = para
|
||||||
|
|
||||||
defp search_options(params, user) do
|
defp search_options(params, user) do
|
||||||
[
|
[
|
||||||
|
skip_relationships: skip_relationships?(params),
|
||||||
resolve: params["resolve"] == "true",
|
resolve: params["resolve"] == "true",
|
||||||
following: params["following"] == "true",
|
following: params["following"] == "true",
|
||||||
limit: ControllerHelper.fetch_integer_param(params, "limit"),
|
limit: fetch_integer_param(params, "limit"),
|
||||||
offset: ControllerHelper.fetch_integer_param(params, "offset"),
|
offset: fetch_integer_param(params, "offset"),
|
||||||
type: params["type"],
|
type: params["type"],
|
||||||
author: get_author(params),
|
author: get_author(params),
|
||||||
for_user: user
|
for_user: user
|
||||||
|
@ -79,12 +81,24 @@ defp search_options(params, user) do
|
||||||
|
|
||||||
defp resource_search(_, "accounts", query, options) do
|
defp resource_search(_, "accounts", query, options) do
|
||||||
accounts = with_fallback(fn -> User.search(query, options) end)
|
accounts = with_fallback(fn -> User.search(query, options) end)
|
||||||
AccountView.render("index.json", users: accounts, for: options[:for_user], as: :user)
|
|
||||||
|
AccountView.render("index.json",
|
||||||
|
users: accounts,
|
||||||
|
for: options[:for_user],
|
||||||
|
as: :user,
|
||||||
|
skip_relationships: false
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp resource_search(_, "statuses", query, options) do
|
defp resource_search(_, "statuses", query, options) do
|
||||||
statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end)
|
statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end)
|
||||||
StatusView.render("index.json", activities: statuses, for: options[:for_user], as: :activity)
|
|
||||||
|
StatusView.render("index.json",
|
||||||
|
activities: statuses,
|
||||||
|
for: options[:for_user],
|
||||||
|
as: :activity,
|
||||||
|
skip_relationships: options[:skip_relationships]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp resource_search(:v2, "hashtags", query, _options) do
|
defp resource_search(:v2, "hashtags", query, _options) do
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
defmodule Pleroma.Web.MastodonAPI.StatusController do
|
defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
import Pleroma.Web.ControllerHelper, only: [try_render: 3, add_link_headers: 2]
|
import Pleroma.Web.ControllerHelper,
|
||||||
|
only: [try_render: 3, add_link_headers: 2, skip_relationships?: 1]
|
||||||
|
|
||||||
require Ecto.Query
|
require Ecto.Query
|
||||||
|
|
||||||
|
@ -101,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
|
|
||||||
`ids` query param is required
|
`ids` query param is required
|
||||||
"""
|
"""
|
||||||
def index(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do
|
def index(%{assigns: %{user: user}} = conn, %{"ids" => ids} = params) do
|
||||||
limit = 100
|
limit = 100
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
|
@ -110,7 +111,12 @@ def index(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do
|
||||||
|> Activity.all_by_ids_with_object()
|
|> Activity.all_by_ids_with_object()
|
||||||
|> Enum.filter(&Visibility.visible_for_user?(&1, user))
|
|> Enum.filter(&Visibility.visible_for_user?(&1, user))
|
||||||
|
|
||||||
render(conn, "index.json", activities: activities, for: user, as: :activity)
|
render(conn, "index.json",
|
||||||
|
activities: activities,
|
||||||
|
for: user,
|
||||||
|
as: :activity,
|
||||||
|
skip_relationships: skip_relationships?(params)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -360,7 +366,12 @@ def favourites(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(activities)
|
|> add_link_headers(activities)
|
||||||
|> render("index.json", activities: activities, for: user, as: :activity)
|
|> render("index.json",
|
||||||
|
activities: activities,
|
||||||
|
for: user,
|
||||||
|
as: :activity,
|
||||||
|
skip_relationships: skip_relationships?(params)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "GET /api/v1/bookmarks"
|
@doc "GET /api/v1/bookmarks"
|
||||||
|
@ -378,6 +389,11 @@ def bookmarks(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(bookmarks)
|
|> add_link_headers(bookmarks)
|
||||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
|> render("index.json",
|
||||||
|
activities: activities,
|
||||||
|
for: user,
|
||||||
|
as: :activity,
|
||||||
|
skip_relationships: skip_relationships?(params)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,10 +5,13 @@
|
||||||
defmodule Pleroma.Web.MastodonAPI.SuggestionController do
|
defmodule Pleroma.Web.MastodonAPI.SuggestionController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :index)
|
||||||
|
|
||||||
@doc "GET /api/v1/suggestions"
|
@doc "GET /api/v1/suggestions"
|
||||||
def index(conn, _) do
|
def index(conn, params),
|
||||||
json(conn, [])
|
do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
import Pleroma.Web.ControllerHelper,
|
import Pleroma.Web.ControllerHelper,
|
||||||
only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1]
|
only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1, skip_relationships?: 1]
|
||||||
|
|
||||||
alias Pleroma.Pagination
|
alias Pleroma.Pagination
|
||||||
alias Pleroma.Plugs.OAuthScopesPlug
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
|
@ -14,9 +14,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
|
||||||
# TODO: Replace with a macro when there is a Phoenix release with
|
# TODO: Replace with a macro when there is a Phoenix release with the following commit in it:
|
||||||
# https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e
|
# https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e
|
||||||
# in it
|
|
||||||
|
|
||||||
plug(RateLimiter, [name: :timeline, bucket_name: :direct_timeline] when action == :direct)
|
plug(RateLimiter, [name: :timeline, bucket_name: :direct_timeline] when action == :direct)
|
||||||
plug(RateLimiter, [name: :timeline, bucket_name: :public_timeline] when action == :public)
|
plug(RateLimiter, [name: :timeline, bucket_name: :public_timeline] when action == :public)
|
||||||
|
@ -49,7 +48,12 @@ def home(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(activities)
|
|> add_link_headers(activities)
|
||||||
|> render("index.json", activities: activities, for: user, as: :activity)
|
|> render("index.json",
|
||||||
|
activities: activities,
|
||||||
|
for: user,
|
||||||
|
as: :activity,
|
||||||
|
skip_relationships: skip_relationships?(params)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET /api/v1/timelines/direct
|
# GET /api/v1/timelines/direct
|
||||||
|
@ -68,7 +72,12 @@ def direct(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(activities)
|
|> add_link_headers(activities)
|
||||||
|> render("index.json", activities: activities, for: user, as: :activity)
|
|> render("index.json",
|
||||||
|
activities: activities,
|
||||||
|
for: user,
|
||||||
|
as: :activity,
|
||||||
|
skip_relationships: skip_relationships?(params)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET /api/v1/timelines/public
|
# GET /api/v1/timelines/public
|
||||||
|
@ -95,7 +104,12 @@ def public(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(activities, %{"local" => local_only})
|
|> add_link_headers(activities, %{"local" => local_only})
|
||||||
|> render("index.json", activities: activities, for: user, as: :activity)
|
|> render("index.json",
|
||||||
|
activities: activities,
|
||||||
|
for: user,
|
||||||
|
as: :activity,
|
||||||
|
skip_relationships: skip_relationships?(params)
|
||||||
|
)
|
||||||
else
|
else
|
||||||
render_error(conn, :unauthorized, "authorization required for timeline view")
|
render_error(conn, :unauthorized, "authorization required for timeline view")
|
||||||
end
|
end
|
||||||
|
@ -140,7 +154,12 @@ def hashtag(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(activities, %{"local" => local_only})
|
|> add_link_headers(activities, %{"local" => local_only})
|
||||||
|> render("index.json", activities: activities, for: user, as: :activity)
|
|> render("index.json",
|
||||||
|
activities: activities,
|
||||||
|
for: user,
|
||||||
|
as: :activity,
|
||||||
|
skip_relationships: skip_relationships?(params)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET /api/v1/timelines/list/:list_id
|
# GET /api/v1/timelines/list/:list_id
|
||||||
|
@ -164,7 +183,12 @@ def list(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
|
||||||
|> ActivityPub.fetch_activities_bounded(following, params)
|
|> ActivityPub.fetch_activities_bounded(following, params)
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|
|
||||||
render(conn, "index.json", activities: activities, for: user, as: :activity)
|
render(conn, "index.json",
|
||||||
|
activities: activities,
|
||||||
|
for: user,
|
||||||
|
as: :activity,
|
||||||
|
skip_relationships: skip_relationships?(params)
|
||||||
|
)
|
||||||
else
|
else
|
||||||
_e -> render_error(conn, :forbidden, "Error.")
|
_e -> render_error(conn, :forbidden, "Error.")
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
||||||
def render("index.json", %{users: users} = opts) do
|
def render("index.json", %{users: users} = opts) do
|
||||||
reading_user = opts[:for]
|
reading_user = opts[:for]
|
||||||
|
|
||||||
|
# Note: :skip_relationships option is currently intentionally not supported for accounts
|
||||||
relationships_opt =
|
relationships_opt =
|
||||||
cond do
|
cond do
|
||||||
Map.has_key?(opts, :relationships) ->
|
Map.has_key?(opts, :relationships) ->
|
||||||
|
@ -180,23 +181,25 @@ defp do_render("show.json", %{user: user} = opts) do
|
||||||
bot = user.actor_type in ["Application", "Service"]
|
bot = user.actor_type in ["Application", "Service"]
|
||||||
|
|
||||||
emojis =
|
emojis =
|
||||||
(user.source_data["tag"] || [])
|
Enum.map(user.emoji, fn {shortcode, url} ->
|
||||||
|> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
|
|
||||||
|> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
|
|
||||||
%{
|
%{
|
||||||
"shortcode" => String.trim(name, ":"),
|
"shortcode" => shortcode,
|
||||||
"url" => MediaProxy.url(url),
|
"url" => url,
|
||||||
"static_url" => MediaProxy.url(url),
|
"static_url" => url,
|
||||||
"visible_in_picker" => false
|
"visible_in_picker" => false
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
relationship =
|
relationship =
|
||||||
|
if opts[:skip_relationships] do
|
||||||
|
%{}
|
||||||
|
else
|
||||||
render("relationship.json", %{
|
render("relationship.json", %{
|
||||||
user: opts[:for],
|
user: opts[:for],
|
||||||
target: user,
|
target: user,
|
||||||
relationships: opts[:relationships]
|
relationships: opts[:relationships]
|
||||||
})
|
})
|
||||||
|
end
|
||||||
|
|
||||||
%{
|
%{
|
||||||
id: to_string(user.id),
|
id: to_string(user.id),
|
||||||
|
|
|
@ -7,6 +7,21 @@ defmodule Pleroma.Web.MastodonAPI.AppView do
|
||||||
|
|
||||||
alias Pleroma.Web.OAuth.App
|
alias Pleroma.Web.OAuth.App
|
||||||
|
|
||||||
|
def render("index.json", %{apps: apps, count: count, page_size: page_size, admin: true}) do
|
||||||
|
%{
|
||||||
|
apps: render_many(apps, Pleroma.Web.MastodonAPI.AppView, "show.json", %{admin: true}),
|
||||||
|
count: count,
|
||||||
|
page_size: page_size
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("show.json", %{admin: true, app: %App{} = app} = assigns) do
|
||||||
|
"show.json"
|
||||||
|
|> render(Map.delete(assigns, :admin))
|
||||||
|
|> Map.put(:trusted, app.trusted)
|
||||||
|
|> Map.put(:id, app.id)
|
||||||
|
end
|
||||||
|
|
||||||
def render("show.json", %{app: %App{} = app}) do
|
def render("show.json", %{app: %App{} = app}) do
|
||||||
%{
|
%{
|
||||||
id: app.id |> to_string,
|
id: app.id |> to_string,
|
||||||
|
|
|
@ -51,14 +51,15 @@ def render("index.json", %{notifications: notifications, for: reading_user} = op
|
||||||
|> Enum.filter(& &1)
|
|> Enum.filter(& &1)
|
||||||
|> Kernel.++(move_activities_targets)
|
|> Kernel.++(move_activities_targets)
|
||||||
|
|
||||||
UserRelationship.view_relationships_option(reading_user, actors)
|
UserRelationship.view_relationships_option(reading_user, actors,
|
||||||
|
source_mutes_only: opts[:skip_relationships]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
opts = %{
|
opts =
|
||||||
for: reading_user,
|
opts
|
||||||
parent_activities: parent_activities,
|
|> Map.put(:parent_activities, parent_activities)
|
||||||
relationships: relationships_opt
|
|> Map.put(:relationships, relationships_opt)
|
||||||
}
|
|
||||||
|
|
||||||
safe_render_many(notifications, NotificationView, "show.json", opts)
|
safe_render_many(notifications, NotificationView, "show.json", opts)
|
||||||
end
|
end
|
||||||
|
@ -82,12 +83,16 @@ def render(
|
||||||
|
|
||||||
mastodon_type = Activity.mastodon_notification_type(activity)
|
mastodon_type = Activity.mastodon_notification_type(activity)
|
||||||
|
|
||||||
|
render_opts = %{
|
||||||
|
relationships: opts[:relationships],
|
||||||
|
skip_relationships: opts[:skip_relationships]
|
||||||
|
}
|
||||||
|
|
||||||
with %{id: _} = account <-
|
with %{id: _} = account <-
|
||||||
AccountView.render("show.json", %{
|
AccountView.render(
|
||||||
user: actor,
|
"show.json",
|
||||||
for: reading_user,
|
Map.merge(render_opts, %{user: actor, for: reading_user})
|
||||||
relationships: opts[:relationships]
|
) do
|
||||||
}) do
|
|
||||||
response = %{
|
response = %{
|
||||||
id: to_string(notification.id),
|
id: to_string(notification.id),
|
||||||
type: mastodon_type,
|
type: mastodon_type,
|
||||||
|
@ -98,8 +103,6 @@ def render(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render_opts = %{relationships: opts[:relationships]}
|
|
||||||
|
|
||||||
case mastodon_type do
|
case mastodon_type do
|
||||||
"mention" ->
|
"mention" ->
|
||||||
put_status(response, activity, reading_user, render_opts)
|
put_status(response, activity, reading_user, render_opts)
|
||||||
|
@ -111,6 +114,7 @@ def render(
|
||||||
put_status(response, parent_activity_fn.(), reading_user, render_opts)
|
put_status(response, parent_activity_fn.(), reading_user, render_opts)
|
||||||
|
|
||||||
"move" ->
|
"move" ->
|
||||||
|
# Note: :skip_relationships option being applied to _account_ rendering (here)
|
||||||
put_target(response, activity, reading_user, render_opts)
|
put_target(response, activity, reading_user, render_opts)
|
||||||
|
|
||||||
"follow" ->
|
"follow" ->
|
||||||
|
|
|
@ -99,7 +99,9 @@ def render("index.json", opts) do
|
||||||
true ->
|
true ->
|
||||||
actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"]))
|
actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"]))
|
||||||
|
|
||||||
UserRelationship.view_relationships_option(reading_user, actors)
|
UserRelationship.view_relationships_option(reading_user, actors,
|
||||||
|
source_mutes_only: opts[:skip_relationships]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
opts =
|
opts =
|
||||||
|
@ -153,7 +155,8 @@ def render(
|
||||||
AccountView.render("show.json", %{
|
AccountView.render("show.json", %{
|
||||||
user: user,
|
user: user,
|
||||||
for: opts[:for],
|
for: opts[:for],
|
||||||
relationships: opts[:relationships]
|
relationships: opts[:relationships],
|
||||||
|
skip_relationships: opts[:skip_relationships]
|
||||||
}),
|
}),
|
||||||
in_reply_to_id: nil,
|
in_reply_to_id: nil,
|
||||||
in_reply_to_account_id: nil,
|
in_reply_to_account_id: nil,
|
||||||
|
@ -301,6 +304,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
_ -> []
|
_ -> []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Status muted state (would do 1 request per status unless user mutes are preloaded)
|
||||||
muted =
|
muted =
|
||||||
thread_muted? ||
|
thread_muted? ||
|
||||||
UserRelationship.exists?(
|
UserRelationship.exists?(
|
||||||
|
@ -319,7 +323,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
AccountView.render("show.json", %{
|
AccountView.render("show.json", %{
|
||||||
user: user,
|
user: user,
|
||||||
for: opts[:for],
|
for: opts[:for],
|
||||||
relationships: opts[:relationships]
|
relationships: opts[:relationships],
|
||||||
|
skip_relationships: opts[:skip_relationships]
|
||||||
}),
|
}),
|
||||||
in_reply_to_id: reply_to && to_string(reply_to.id),
|
in_reply_to_id: reply_to && to_string(reply_to.id),
|
||||||
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
|
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
defmodule Pleroma.Web.OAuth.App do
|
defmodule Pleroma.Web.OAuth.App do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
|
@ -16,14 +17,24 @@ defmodule Pleroma.Web.OAuth.App do
|
||||||
field(:website, :string)
|
field(:website, :string)
|
||||||
field(:client_id, :string)
|
field(:client_id, :string)
|
||||||
field(:client_secret, :string)
|
field(:client_secret, :string)
|
||||||
|
field(:trusted, :boolean, default: false)
|
||||||
|
|
||||||
|
has_many(:oauth_authorizations, Pleroma.Web.OAuth.Authorization, on_delete: :delete_all)
|
||||||
|
has_many(:oauth_tokens, Pleroma.Web.OAuth.Token, on_delete: :delete_all)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec changeset(App.t(), map()) :: Ecto.Changeset.t()
|
||||||
|
def changeset(struct, params) do
|
||||||
|
cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted])
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec register_changeset(App.t(), map()) :: Ecto.Changeset.t()
|
||||||
def register_changeset(struct, params \\ %{}) do
|
def register_changeset(struct, params \\ %{}) do
|
||||||
changeset =
|
changeset =
|
||||||
struct
|
struct
|
||||||
|> cast(params, [:client_name, :redirect_uris, :scopes, :website])
|
|> changeset(params)
|
||||||
|> validate_required([:client_name, :redirect_uris, :scopes])
|
|> validate_required([:client_name, :redirect_uris, :scopes])
|
||||||
|
|
||||||
if changeset.valid? do
|
if changeset.valid? do
|
||||||
|
@ -41,6 +52,21 @@ def register_changeset(struct, params \\ %{}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec create(map()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
|
||||||
|
def create(params) do
|
||||||
|
with changeset <- __MODULE__.register_changeset(%__MODULE__{}, params) do
|
||||||
|
Repo.insert(changeset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec update(map()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
|
||||||
|
def update(params) do
|
||||||
|
with %__MODULE__{} = app <- Repo.get(__MODULE__, params["id"]),
|
||||||
|
changeset <- changeset(app, params) do
|
||||||
|
Repo.update(changeset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets app by attrs or create new with attrs.
|
Gets app by attrs or create new with attrs.
|
||||||
And updates the scopes if need.
|
And updates the scopes if need.
|
||||||
|
@ -65,4 +91,58 @@ defp update_scopes(%__MODULE__{} = app, scopes) do
|
||||||
|> change(%{scopes: scopes})
|
|> change(%{scopes: scopes})
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec search(map()) :: {:ok, [App.t()], non_neg_integer()}
|
||||||
|
def search(params) do
|
||||||
|
query = from(a in __MODULE__)
|
||||||
|
|
||||||
|
query =
|
||||||
|
if params[:client_name] do
|
||||||
|
from(a in query, where: a.client_name == ^params[:client_name])
|
||||||
|
else
|
||||||
|
query
|
||||||
|
end
|
||||||
|
|
||||||
|
query =
|
||||||
|
if params[:client_id] do
|
||||||
|
from(a in query, where: a.client_id == ^params[:client_id])
|
||||||
|
else
|
||||||
|
query
|
||||||
|
end
|
||||||
|
|
||||||
|
query =
|
||||||
|
if Map.has_key?(params, :trusted) do
|
||||||
|
from(a in query, where: a.trusted == ^params[:trusted])
|
||||||
|
else
|
||||||
|
query
|
||||||
|
end
|
||||||
|
|
||||||
|
query =
|
||||||
|
from(u in query,
|
||||||
|
limit: ^params[:page_size],
|
||||||
|
offset: ^((params[:page] - 1) * params[:page_size])
|
||||||
|
)
|
||||||
|
|
||||||
|
count = Repo.aggregate(__MODULE__, :count, :id)
|
||||||
|
|
||||||
|
{:ok, Repo.all(query), count}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec destroy(pos_integer()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
|
||||||
|
def destroy(id) do
|
||||||
|
with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do
|
||||||
|
Repo.delete(app)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec errors(Ecto.Changeset.t()) :: map()
|
||||||
|
def errors(changeset) do
|
||||||
|
Enum.reduce(changeset.errors, %{}, fn
|
||||||
|
{:client_name, {error, _}}, acc ->
|
||||||
|
Map.put(acc, :name, error)
|
||||||
|
|
||||||
|
{key, {error, _}}, acc ->
|
||||||
|
Map.put(acc, key, error)
|
||||||
|
end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,6 +27,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||||
plug(:fetch_flash)
|
plug(:fetch_flash)
|
||||||
plug(RateLimiter, [name: :authentication] when action == :create_authorization)
|
plug(RateLimiter, [name: :authentication] when action == :create_authorization)
|
||||||
|
|
||||||
|
plug(:skip_plug, Pleroma.Plugs.OAuthScopesPlug)
|
||||||
|
|
||||||
action_fallback(Pleroma.Web.OAuth.FallbackController)
|
action_fallback(Pleroma.Web.OAuth.FallbackController)
|
||||||
|
|
||||||
@oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob"
|
@oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob"
|
||||||
|
|
|
@ -6,14 +6,13 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
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, skip_relationships?: 1]
|
||||||
|
|
||||||
alias Ecto.Changeset
|
alias Ecto.Changeset
|
||||||
alias Pleroma.Plugs.OAuthScopesPlug
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
alias Pleroma.Plugs.RateLimiter
|
alias Pleroma.Plugs.RateLimiter
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.CommonAPI
|
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
@ -58,38 +57,32 @@ def confirmation_resend(conn, params) do
|
||||||
|
|
||||||
@doc "PATCH /api/v1/pleroma/accounts/update_avatar"
|
@doc "PATCH /api/v1/pleroma/accounts/update_avatar"
|
||||||
def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
|
def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
|
||||||
{:ok, user} =
|
{:ok, _user} =
|
||||||
user
|
user
|
||||||
|> Changeset.change(%{avatar: nil})
|
|> Changeset.change(%{avatar: nil})
|
||||||
|> User.update_and_set_cache()
|
|> User.update_and_set_cache()
|
||||||
|
|
||||||
CommonAPI.update(user)
|
|
||||||
|
|
||||||
json(conn, %{url: nil})
|
json(conn, %{url: nil})
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_avatar(%{assigns: %{user: user}} = conn, params) do
|
def update_avatar(%{assigns: %{user: user}} = conn, params) do
|
||||||
{:ok, %{data: data}} = ActivityPub.upload(params, type: :avatar)
|
{:ok, %{data: data}} = ActivityPub.upload(params, type: :avatar)
|
||||||
{:ok, user} = user |> Changeset.change(%{avatar: data}) |> User.update_and_set_cache()
|
{:ok, _user} = user |> Changeset.change(%{avatar: data}) |> User.update_and_set_cache()
|
||||||
%{"url" => [%{"href" => href} | _]} = data
|
%{"url" => [%{"href" => href} | _]} = data
|
||||||
|
|
||||||
CommonAPI.update(user)
|
|
||||||
|
|
||||||
json(conn, %{url: href})
|
json(conn, %{url: href})
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "PATCH /api/v1/pleroma/accounts/update_banner"
|
@doc "PATCH /api/v1/pleroma/accounts/update_banner"
|
||||||
def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
|
def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
|
||||||
with {:ok, user} <- User.update_banner(user, %{}) do
|
with {:ok, _user} <- User.update_banner(user, %{}) do
|
||||||
CommonAPI.update(user)
|
|
||||||
json(conn, %{url: nil})
|
json(conn, %{url: nil})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_banner(%{assigns: %{user: user}} = conn, params) do
|
def update_banner(%{assigns: %{user: user}} = conn, params) do
|
||||||
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
|
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
|
||||||
{:ok, user} <- User.update_banner(user, object.data) do
|
{:ok, _user} <- User.update_banner(user, object.data) do
|
||||||
CommonAPI.update(user)
|
|
||||||
%{"url" => [%{"href" => href} | _]} = object.data
|
%{"url" => [%{"href" => href} | _]} = object.data
|
||||||
|
|
||||||
json(conn, %{url: href})
|
json(conn, %{url: href})
|
||||||
|
@ -139,7 +132,12 @@ def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(activities)
|
|> add_link_headers(activities)
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> render("index.json", activities: activities, for: for_user, as: :activity)
|
|> render("index.json",
|
||||||
|
activities: activities,
|
||||||
|
for: for_user,
|
||||||
|
as: :activity,
|
||||||
|
skip_relationships: skip_relationships?(params)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "POST /api/v1/pleroma/accounts/:id/subscribe"
|
@doc "POST /api/v1/pleroma/accounts/:id/subscribe"
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
|
defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
|
@ -34,7 +34,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["write:conversations"]} when action == :update_conversation
|
%{scopes: ["write:conversations"]} when action in [:update_conversation, :read_conversations]
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :read_notification)
|
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :read_notification)
|
||||||
|
@ -130,7 +130,12 @@ def conversation_statuses(
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(activities)
|
|> add_link_headers(activities)
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
|> render("index.json",
|
||||||
|
activities: activities,
|
||||||
|
for: user,
|
||||||
|
as: :activity,
|
||||||
|
skip_relationships: skip_relationships?(params)
|
||||||
|
)
|
||||||
else
|
else
|
||||||
_error ->
|
_error ->
|
||||||
conn
|
conn
|
||||||
|
@ -184,13 +189,17 @@ def read_notification(%{assigns: %{user: user}} = conn, %{"id" => notification_i
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do
|
def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id} = params) do
|
||||||
with notifications <- Notification.set_read_up_to(user, max_id) do
|
with notifications <- Notification.set_read_up_to(user, max_id) do
|
||||||
notifications = Enum.take(notifications, 80)
|
notifications = Enum.take(notifications, 80)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(NotificationView)
|
|> put_view(NotificationView)
|
||||||
|> render("index.json", %{notifications: notifications, for: user})
|
|> render("index.json",
|
||||||
|
notifications: notifications,
|
||||||
|
for: user,
|
||||||
|
skip_relationships: skip_relationships?(params)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -64,5 +64,8 @@ def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) d
|
||||||
|
|
||||||
def fetch_data_for_activity(_), do: %{}
|
def fetch_data_for_activity(_), do: %{}
|
||||||
|
|
||||||
def perform(:fetch, %Activity{} = activity), do: fetch_data_for_activity(activity)
|
def perform(:fetch, %Activity{} = activity) do
|
||||||
|
fetch_data_for_activity(activity)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -35,6 +35,7 @@ defmodule Pleroma.Web.Router do
|
||||||
pipeline :authenticated_api do
|
pipeline :authenticated_api do
|
||||||
plug(:accepts, ["json"])
|
plug(:accepts, ["json"])
|
||||||
plug(:fetch_session)
|
plug(:fetch_session)
|
||||||
|
plug(Pleroma.Plugs.AuthExpectedPlug)
|
||||||
plug(Pleroma.Plugs.OAuthPlug)
|
plug(Pleroma.Plugs.OAuthPlug)
|
||||||
plug(Pleroma.Plugs.BasicAuthDecoderPlug)
|
plug(Pleroma.Plugs.BasicAuthDecoderPlug)
|
||||||
plug(Pleroma.Plugs.UserFetcherPlug)
|
plug(Pleroma.Plugs.UserFetcherPlug)
|
||||||
|
@ -203,12 +204,18 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/config", AdminAPIController, :config_show)
|
get("/config", AdminAPIController, :config_show)
|
||||||
post("/config", AdminAPIController, :config_update)
|
post("/config", AdminAPIController, :config_update)
|
||||||
get("/config/descriptions", AdminAPIController, :config_descriptions)
|
get("/config/descriptions", AdminAPIController, :config_descriptions)
|
||||||
|
get("/need_reboot", AdminAPIController, :need_reboot)
|
||||||
get("/restart", AdminAPIController, :restart)
|
get("/restart", AdminAPIController, :restart)
|
||||||
|
|
||||||
get("/moderation_log", AdminAPIController, :list_log)
|
get("/moderation_log", AdminAPIController, :list_log)
|
||||||
|
|
||||||
post("/reload_emoji", AdminAPIController, :reload_emoji)
|
post("/reload_emoji", AdminAPIController, :reload_emoji)
|
||||||
get("/stats", AdminAPIController, :stats)
|
get("/stats", AdminAPIController, :stats)
|
||||||
|
|
||||||
|
get("/oauth_app", AdminAPIController, :oauth_app_list)
|
||||||
|
post("/oauth_app", AdminAPIController, :oauth_app_create)
|
||||||
|
patch("/oauth_app/:id", AdminAPIController, :oauth_app_update)
|
||||||
|
delete("/oauth_app/:id", AdminAPIController, :oauth_app_delete)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
|
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
|
||||||
|
@ -338,7 +345,7 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/accounts/relationships", AccountController, :relationships)
|
get("/accounts/relationships", AccountController, :relationships)
|
||||||
|
|
||||||
get("/accounts/:id/lists", AccountController, :lists)
|
get("/accounts/:id/lists", AccountController, :lists)
|
||||||
get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array)
|
get("/accounts/:id/identity_proofs", AccountController, :identity_proofs)
|
||||||
|
|
||||||
get("/follow_requests", FollowRequestController, :index)
|
get("/follow_requests", FollowRequestController, :index)
|
||||||
get("/blocks", AccountController, :blocks)
|
get("/blocks", AccountController, :blocks)
|
||||||
|
@ -671,6 +678,17 @@ defmodule Pleroma.Web.Router do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Test-only routes needed to test action dispatching and plug chain execution
|
||||||
|
if Pleroma.Config.get(:env) == :test do
|
||||||
|
scope "/test/authenticated_api", Pleroma.Tests do
|
||||||
|
pipe_through(:authenticated_api)
|
||||||
|
|
||||||
|
for action <- [:skipped_oauth, :performed_oauth, :missed_oauth] do
|
||||||
|
get("/#{action}", OAuthTestController, action)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
scope "/", Pleroma.Web.MongooseIM do
|
scope "/", Pleroma.Web.MongooseIM do
|
||||||
get("/user_exists", MongooseIMController, :user_exists)
|
get("/user_exists", MongooseIMController, :user_exists)
|
||||||
get("/check_password", MongooseIMController, :check_password)
|
get("/check_password", MongooseIMController, :check_password)
|
||||||
|
|
|
@ -18,15 +18,6 @@ defmodule Pleroma.Web.StaticFE.StaticFEView do
|
||||||
|
|
||||||
@media_types ["image", "audio", "video"]
|
@media_types ["image", "audio", "video"]
|
||||||
|
|
||||||
def emoji_for_user(%User{} = user) do
|
|
||||||
user.source_data
|
|
||||||
|> Map.get("tag", [])
|
|
||||||
|> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
|
|
||||||
|> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
|
|
||||||
{String.trim(name, ":"), url}
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch_media_type(%{"mediaType" => mediaType}) do
|
def fetch_media_type(%{"mediaType" => mediaType}) do
|
||||||
Utils.fetch_media_type(@media_types, mediaType)
|
Utils.fetch_media_type(@media_types, mediaType)
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
<id><%= @data["id"] %></id>
|
<id><%= @data["id"] %></id>
|
||||||
<title><%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %></title>
|
<title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
|
||||||
<content type="html"><%= activity_content(@object) %></content>
|
<content type="html"><%= activity_content(@data) %></content>
|
||||||
<published><%= @data["published"] %></published>
|
<published><%= @activity.data["published"] %></published>
|
||||||
<updated><%= @data["published"] %></updated>
|
<updated><%= @activity.data["published"] %></updated>
|
||||||
<ostatus:conversation ref="<%= activity_context(@activity) %>">
|
<ostatus:conversation ref="<%= activity_context(@activity) %>">
|
||||||
<%= activity_context(@activity) %>
|
<%= activity_context(@activity) %>
|
||||||
</ostatus:conversation>
|
</ostatus:conversation>
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
<guid><%= @data["id"] %></guid>
|
<guid><%= @data["id"] %></guid>
|
||||||
<title><%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %></title>
|
<title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
|
||||||
<description><%= activity_content(@object) %></description>
|
<description><%= activity_content(@data) %></description>
|
||||||
<pubDate><%= @data["published"] %></pubDate>
|
<pubDate><%= @activity.data["published"] %></pubDate>
|
||||||
<updated><%= @data["published"] %></updated>
|
<updated><%= @activity.data["published"] %></updated>
|
||||||
<ostatus:conversation ref="<%= activity_context(@activity) %>">
|
<ostatus:conversation ref="<%= activity_context(@activity) %>">
|
||||||
<%= activity_context(@activity) %>
|
<%= activity_context(@activity) %>
|
||||||
</ostatus:conversation>
|
</ostatus:conversation>
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
<%= render @view_module, "_tag_author.atom", assigns %>
|
<%= render @view_module, "_tag_author.atom", assigns %>
|
||||||
|
|
||||||
<id><%= @data["id"] %></id>
|
<id><%= @data["id"] %></id>
|
||||||
<title><%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %></title>
|
<title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
|
||||||
<content type="html"><%= activity_content(@object) %></content>
|
<content type="html"><%= activity_content(@data) %></content>
|
||||||
|
|
||||||
<%= if @activity.local do %>
|
<%= if @activity.local do %>
|
||||||
<link type="application/atom+xml" href='<%= @data["id"] %>' rel="self"/>
|
<link type="application/atom+xml" href='<%= @data["id"] %>' rel="self"/>
|
||||||
|
@ -15,8 +15,8 @@
|
||||||
<link type="text/html" href='<%= @data["external_url"] %>' rel="alternate"/>
|
<link type="text/html" href='<%= @data["external_url"] %>' rel="alternate"/>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<published><%= @data["published"] %></published>
|
<published><%= @activity.data["published"] %></published>
|
||||||
<updated><%= @data["published"] %></updated>
|
<updated><%= @activity.data["published"] %></updated>
|
||||||
|
|
||||||
<ostatus:conversation ref="<%= activity_context(@activity) %>">
|
<ostatus:conversation ref="<%= activity_context(@activity) %>">
|
||||||
<%= activity_context(@activity) %>
|
<%= activity_context(@activity) %>
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
<item>
|
<item>
|
||||||
<title><%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %></title>
|
<title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
|
||||||
|
|
||||||
|
|
||||||
<guid isPermalink="true"><%= activity_context(@activity) %></guid>
|
<guid isPermalink="true"><%= activity_context(@activity) %></guid>
|
||||||
<link><%= activity_context(@activity) %></link>
|
<link><%= activity_context(@activity) %></link>
|
||||||
<pubDate><%= pub_date(@data["published"]) %></pubDate>
|
<pubDate><%= pub_date(@activity.data["published"]) %></pubDate>
|
||||||
|
|
||||||
<description><%= activity_content(@object) %></description>
|
<description><%= activity_content(@data) %></description>
|
||||||
<%= for attachment <- @data["attachment"] || [] do %>
|
<%= for attachment <- @data["attachment"] || [] do %>
|
||||||
<enclosure url="<%= attachment_href(attachment) %>" type="<%= attachment_type(attachment) %>"/>
|
<enclosure url="<%= attachment_href(attachment) %>" type="<%= attachment_type(attachment) %>"/>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<img src="<%= User.avatar_url(@user) |> MediaProxy.url %>" width="48" height="48" alt="">
|
<img src="<%= User.avatar_url(@user) |> MediaProxy.url %>" width="48" height="48" alt="">
|
||||||
</div>
|
</div>
|
||||||
<span class="display-name">
|
<span class="display-name">
|
||||||
<bdi><%= raw (@user.name |> Formatter.emojify(emoji_for_user(@user))) %></bdi>
|
<bdi><%= raw Formatter.emojify(@user.name, @user.emoji) %></bdi>
|
||||||
<span class="nickname"><%= @user.nickname %></span>
|
<span class="nickname"><%= @user.nickname %></span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<input type="hidden" name="profile" value="">
|
<input type="hidden" name="profile" value="">
|
||||||
<button type="submit" class="collapse">Remote follow</button>
|
<button type="submit" class="collapse">Remote follow</button>
|
||||||
</form>
|
</form>
|
||||||
<%= raw Formatter.emojify(@user.name, emoji_for_user(@user)) %> |
|
<%= raw Formatter.emojify(@user.name, @user.emoji) %> |
|
||||||
<%= link "@#{@user.nickname}@#{Endpoint.host()}", to: (@user.uri || @user.ap_id) %>
|
<%= link "@#{@user.nickname}@#{Endpoint.host()}", to: (@user.uri || @user.ap_id) %>
|
||||||
</h3>
|
</h3>
|
||||||
<p><%= raw @user.bio %></p>
|
<p><%= raw @user.bio %></p>
|
||||||
|
|
|
@ -13,6 +13,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
||||||
|
|
||||||
def register_user(params, opts \\ []) do
|
def register_user(params, opts \\ []) do
|
||||||
token = params["token"]
|
token = params["token"]
|
||||||
|
trusted_app? = params["trusted_app"]
|
||||||
|
|
||||||
params = %{
|
params = %{
|
||||||
nickname: params["nickname"],
|
nickname: params["nickname"],
|
||||||
|
@ -29,7 +30,7 @@ def register_user(params, opts \\ []) do
|
||||||
captcha_enabled = Pleroma.Config.get([Pleroma.Captcha, :enabled])
|
captcha_enabled = Pleroma.Config.get([Pleroma.Captcha, :enabled])
|
||||||
# true if captcha is disabled or enabled and valid, false otherwise
|
# true if captcha is disabled or enabled and valid, false otherwise
|
||||||
captcha_ok =
|
captcha_ok =
|
||||||
if not captcha_enabled do
|
if trusted_app? || not captcha_enabled do
|
||||||
:ok
|
:ok
|
||||||
else
|
else
|
||||||
Pleroma.Captcha.validate(
|
Pleroma.Captcha.validate(
|
||||||
|
|
|
@ -15,6 +15,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
||||||
|
|
||||||
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
|
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
|
||||||
|
|
||||||
|
plug(:skip_plug, OAuthScopesPlug when action in [:oauth_tokens, :revoke_token])
|
||||||
|
|
||||||
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
|
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
|
||||||
|
|
||||||
action_fallback(:errors)
|
action_fallback(:errors)
|
||||||
|
|
|
@ -29,11 +29,45 @@ def controller do
|
||||||
import Pleroma.Web.Router.Helpers
|
import Pleroma.Web.Router.Helpers
|
||||||
import Pleroma.Web.TranslationHelpers
|
import Pleroma.Web.TranslationHelpers
|
||||||
|
|
||||||
|
alias Pleroma.Plugs.PlugHelper
|
||||||
|
|
||||||
plug(:set_put_layout)
|
plug(:set_put_layout)
|
||||||
|
|
||||||
defp set_put_layout(conn, _) do
|
defp set_put_layout(conn, _) do
|
||||||
put_layout(conn, Pleroma.Config.get(:app_layout, "app.html"))
|
put_layout(conn, Pleroma.Config.get(:app_layout, "app.html"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain
|
||||||
|
defp skip_plug(conn, plug_module) do
|
||||||
|
try do
|
||||||
|
plug_module.skip_plug(conn)
|
||||||
|
rescue
|
||||||
|
UndefinedFunctionError ->
|
||||||
|
raise "#{plug_module} is not skippable. Append `use Pleroma.Web, :plug` to its code."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Executed just before actual controller action, invokes before-action hooks (callbacks)
|
||||||
|
defp action(conn, params) do
|
||||||
|
with %Plug.Conn{halted: false} <- maybe_halt_on_missing_oauth_scopes_check(conn) do
|
||||||
|
super(conn, params)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Halts if authenticated API action neither performs nor explicitly skips OAuth scopes check
|
||||||
|
defp maybe_halt_on_missing_oauth_scopes_check(conn) do
|
||||||
|
if Pleroma.Plugs.AuthExpectedPlug.auth_expected?(conn) &&
|
||||||
|
not PlugHelper.plug_called_or_skipped?(conn, Pleroma.Plugs.OAuthScopesPlug) do
|
||||||
|
conn
|
||||||
|
|> render_error(
|
||||||
|
:forbidden,
|
||||||
|
"Security violation: OAuth scopes check was neither handled nor explicitly skipped."
|
||||||
|
)
|
||||||
|
|> halt()
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -96,6 +130,35 @@ def channel do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def plug do
|
||||||
|
quote do
|
||||||
|
alias Pleroma.Plugs.PlugHelper
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain.
|
||||||
|
"""
|
||||||
|
def skip_plug(conn) do
|
||||||
|
PlugHelper.append_to_private_list(
|
||||||
|
conn,
|
||||||
|
PlugHelper.skipped_plugs_list_id(),
|
||||||
|
__MODULE__
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Plug
|
||||||
|
@doc "If marked as skipped, returns `conn`, and calls `perform/2` otherwise."
|
||||||
|
def call(%Plug.Conn{} = conn, options) do
|
||||||
|
if PlugHelper.plug_skipped?(conn, __MODULE__) do
|
||||||
|
conn
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
|> PlugHelper.append_to_private_list(PlugHelper.called_plugs_list_id(), __MODULE__)
|
||||||
|
|> perform(options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
When used, dispatch to the appropriate controller/view/etc.
|
When used, dispatch to the appropriate controller/view/etc.
|
||||||
"""
|
"""
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -183,7 +183,7 @@ defp deps do
|
||||||
{:flake_id, "~> 0.1.0"},
|
{:flake_id, "~> 0.1.0"},
|
||||||
{:remote_ip,
|
{:remote_ip,
|
||||||
git: "https://git.pleroma.social/pleroma/remote_ip.git",
|
git: "https://git.pleroma.social/pleroma/remote_ip.git",
|
||||||
ref: "825dc00aaba5a1b7c4202a532b696b595dd3bcb3"},
|
ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"},
|
||||||
{:captcha,
|
{:captcha,
|
||||||
git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
|
git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
|
||||||
ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
|
ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
|
||||||
|
|
2
mix.lock
2
mix.lock
|
@ -97,7 +97,7 @@
|
||||||
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "d736bfa7444112eb840027bb887832a0e403a4a3437f48028c3b29a2dbbd2543"},
|
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "d736bfa7444112eb840027bb887832a0e403a4a3437f48028c3b29a2dbbd2543"},
|
||||||
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
|
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
|
||||||
"recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm", "72f3840fedd94f06315c523f6cecf5b4827233bed7ae3fe135b2a0ebeab5e196"},
|
"recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm", "72f3840fedd94f06315c523f6cecf5b4827233bed7ae3fe135b2a0ebeab5e196"},
|
||||||
"remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "825dc00aaba5a1b7c4202a532b696b595dd3bcb3", [ref: "825dc00aaba5a1b7c4202a532b696b595dd3bcb3"]},
|
"remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]},
|
||||||
"sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
|
"sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
|
||||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"},
|
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"},
|
||||||
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
|
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
|
||||||
|
|
|
@ -3,7 +3,6 @@ defmodule Pleroma.Repo.Migrations.MigrateOldBookmarks do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Bookmark
|
alias Pleroma.Bookmark
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
|
||||||
def up do
|
def up do
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
defmodule Pleroma.Repo.Migrations.CreateSafeJsonbSet do
|
defmodule Pleroma.Repo.Migrations.CreateSafeJsonbSet do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
alias Pleroma.User
|
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
execute("""
|
execute("""
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddTrustedToApps do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:apps) do
|
||||||
|
add(:trusted, :boolean, default: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
17
priv/repo/migrations/20200401030751_users_add_public_key.exs
Normal file
17
priv/repo/migrations/20200401030751_users_add_public_key.exs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.UsersAddPublicKey do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
alter table(:users) do
|
||||||
|
add_if_not_exists(:public_key, :text)
|
||||||
|
end
|
||||||
|
|
||||||
|
execute("UPDATE users SET public_key = source_data->'publicKey'->>'publicKeyPem'")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:users) do
|
||||||
|
remove_if_exists(:public_key, :text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
20
priv/repo/migrations/20200401072456_users_add_inboxes.exs
Normal file
20
priv/repo/migrations/20200401072456_users_add_inboxes.exs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.UsersAddInboxes do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
alter table(:users) do
|
||||||
|
add_if_not_exists(:inbox, :text)
|
||||||
|
add_if_not_exists(:shared_inbox, :text)
|
||||||
|
end
|
||||||
|
|
||||||
|
execute("UPDATE users SET inbox = source_data->>'inbox'")
|
||||||
|
execute("UPDATE users SET shared_inbox = source_data->'endpoints'->>'sharedInbox'")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:users) do
|
||||||
|
remove_if_exists(:inbox, :text)
|
||||||
|
remove_if_exists(:shared_inbox, :text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
38
priv/repo/migrations/20200406100225_users_add_emoji.exs
Normal file
38
priv/repo/migrations/20200406100225_users_add_emoji.exs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.UsersPopulateEmoji do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Repo
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE users ALTER COLUMN emoji SET DEFAULT '{}'::jsonb")
|
||||||
|
execute("UPDATE users SET emoji = DEFAULT WHERE emoji = '[]'::jsonb")
|
||||||
|
|
||||||
|
from(u in User)
|
||||||
|
|> select([u], struct(u, [:id, :ap_id, :source_data]))
|
||||||
|
|> Repo.stream()
|
||||||
|
|> Enum.each(fn user ->
|
||||||
|
emoji =
|
||||||
|
user.source_data
|
||||||
|
|> Map.get("tag", [])
|
||||||
|
|> Enum.filter(fn
|
||||||
|
%{"type" => "Emoji"} -> true
|
||||||
|
_ -> false
|
||||||
|
end)
|
||||||
|
|> Enum.reduce(%{}, fn %{"icon" => %{"url" => url}, "name" => name}, acc ->
|
||||||
|
Map.put(acc, String.trim(name, ":"), url)
|
||||||
|
end)
|
||||||
|
|
||||||
|
user
|
||||||
|
|> Ecto.Changeset.cast(%{emoji: emoji}, [:emoji])
|
||||||
|
|> Repo.update()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE users ALTER COLUMN emoji SET DEFAULT '[]'::jsonb")
|
||||||
|
execute("UPDATE users SET emoji = DEFAULT WHERE emoji = '{}'::jsonb")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.UsersRemoveSourceData do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
alter table(:users) do
|
||||||
|
remove_if_exists(:source_data, :map)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:users) do
|
||||||
|
add_if_not_exists(:source_data, :map, default: %{})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Binary file not shown.
|
@ -1,126 +0,0 @@
|
||||||
<?xml version="1.0" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<metadata>Copyright (C) 2019 by original authors @ fontello.com</metadata>
|
|
||||||
<defs>
|
|
||||||
<font id="fontello" horiz-adv-x="1000" >
|
|
||||||
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="857" descent="-143" />
|
|
||||||
<missing-glyph horiz-adv-x="1000" />
|
|
||||||
<glyph glyph-name="cancel" unicode="" d="M724 119q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
|
|
||||||
|
|
||||||
<glyph glyph-name="upload" unicode="" d="M714 36q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m143 0q0 14-10 25t-26 10-25-10-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-38t-38-16h-821q-23 0-38 16t-16 38v179q0 22 16 38t38 15h238q12-31 39-51t62-20h143q34 0 61 20t40 51h238q22 0 38-15t16-38z m-182 361q-9-22-33-22h-143v-250q0-15-10-25t-25-11h-143q-15 0-25 11t-11 25v250h-143q-23 0-33 22-9 22 8 39l250 250q10 10 25 10t25-10l250-250q18-17 8-39z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="star" unicode="" d="M929 496q0-12-15-27l-202-197 48-279q0-4 0-12 0-11-6-19t-17-9q-10 0-22 7l-251 132-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="star-empty" unicode="" d="M635 297l170 166-235 34-106 213-105-213-236-34 171-166-41-235 211 111 211-111z m294 199q0-12-15-27l-202-197 48-279q0-4 0-12 0-28-23-28-10 0-22 7l-251 132-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="retweet" unicode="" d="M714 18q0-7-5-13t-13-5h-535q-5 0-8 1t-5 4-3 4-2 7 0 6v335h-107q-15 0-25 11t-11 25q0 13 8 23l179 214q11 12 27 12t28-12l178-214q9-10 9-23 0-15-11-25t-25-11h-107v-214h321q9 0 14-6l89-108q4-5 4-11z m357 232q0-13-8-23l-178-214q-12-13-28-13t-27 13l-179 214q-8 10-8 23 0 14 11 25t25 11h107v214h-322q-9 0-14 7l-89 107q-4 5-4 11 0 7 5 12t13 6h536q4 0 7-1t5-4 3-5 2-6 1-7v-334h107q14 0 25-11t10-25z" horiz-adv-x="1071.4" />
|
|
||||||
|
|
||||||
<glyph glyph-name="eye-off" unicode="" d="M310 112l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-91l157 280q4-25 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-85t81-103q11-19 11-39z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="search" unicode="" d="M643 393q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-22-50t-50-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="cog" unicode="" d="M571 357q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 16 20 16h124q7 0 13-5t7-12l15-103q28-9 51-20l79 59q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-12 0-7-4-13-9-12-29-37t-30-40q15-28 23-54l102-16q7-1 12-7t4-13z" horiz-adv-x="857.1" />
|
|
||||||
|
|
||||||
<glyph glyph-name="logout" unicode="" d="M357 53q0-2 1-11t0-14-2-14-5-10-12-4h-178q-67 0-114 47t-47 114v392q0 67 47 114t114 47h178q8 0 13-5t5-13q0-2 1-11t0-15-2-13-5-11-12-3h-178q-37 0-63-26t-27-64v-392q0-37 27-63t63-27h174t6 0 7-2 4-3 4-5 1-8z m518 304q0-14-11-25l-303-304q-11-10-25-10t-25 10-11 25v161h-250q-14 0-25 11t-11 25v214q0 15 11 25t25 11h250v161q0 14 11 25t25 10 25-10l303-304q11-10 11-25z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="down-open" unicode="" d="M939 406l-414-413q-10-11-25-11t-25 11l-414 413q-11 11-11 26t11 25l93 92q10 11 25 11t25-11l296-296 296 296q11 11 25 11t26-11l92-92q11-11 11-25t-11-26z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="attach" unicode="" d="M244-133q-102 0-170 72-72 70-74 166t84 190l496 496q80 80 174 54 44-12 79-47t47-79q26-96-54-176l-474-474q-40-40-88-46-48-4-80 28-30 24-27 74t47 92l332 334q24 26 50 0t0-50l-332-332q-44-44-20-70 12-8 24-6 24 4 46 26l474 474q50 50 34 108-16 60-76 76-54 14-108-36l-494-494q-66-76-64-143t52-117q50-48 117-50t141 62l496 494q24 24 50 0 26-22 0-48l-496-496q-82-82-186-82z" horiz-adv-x="939" />
|
|
||||||
|
|
||||||
<glyph glyph-name="picture" unicode="" d="M357 536q0-45-31-76t-76-32-76 32-31 76 31 76 76 31 76-31 31-76z m572-215v-250h-786v107l178 179 90-89 285 285z m53 393h-893q-7 0-12-5t-6-13v-678q0-7 6-13t12-5h893q7 0 13 5t5 13v678q0 8-5 13t-13 5z m89-18v-678q0-37-26-63t-63-27h-893q-36 0-63 27t-26 63v678q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" />
|
|
||||||
|
|
||||||
<glyph glyph-name="video" unicode="" d="M214-36v72q0 14-10 25t-25 10h-72q-14 0-25-10t-11-25v-72q0-14 11-25t25-11h72q14 0 25 11t10 25z m0 214v72q0 14-10 25t-25 11h-72q-14 0-25-11t-11-25v-72q0-14 11-25t25-10h72q14 0 25 10t10 25z m0 215v71q0 15-10 25t-25 11h-72q-14 0-25-11t-11-25v-71q0-15 11-25t25-11h72q14 0 25 11t10 25z m572-429v286q0 14-11 25t-25 11h-429q-14 0-25-11t-10-25v-286q0-14 10-25t25-11h429q15 0 25 11t11 25z m-572 643v71q0 15-10 26t-25 10h-72q-14 0-25-10t-11-26v-71q0-14 11-25t25-11h72q14 0 25 11t10 25z m786-643v72q0 14-11 25t-25 10h-71q-15 0-25-10t-11-25v-72q0-14 11-25t25-11h71q15 0 25 11t11 25z m-214 429v285q0 15-11 26t-25 10h-429q-14 0-25-10t-10-26v-285q0-15 10-25t25-11h429q15 0 25 11t11 25z m214-215v72q0 14-11 25t-25 11h-71q-15 0-25-11t-11-25v-72q0-14 11-25t25-10h71q15 0 25 10t11 25z m0 215v71q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-71q0-15 11-25t25-11h71q15 0 25 11t11 25z m0 214v71q0 15-11 26t-25 10h-71q-15 0-25-10t-11-26v-71q0-14 11-25t25-11h71q15 0 25 11t11 25z m71 89v-750q0-37-26-63t-63-26h-893q-36 0-63 26t-26 63v750q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" />
|
|
||||||
|
|
||||||
<glyph glyph-name="right-open" unicode="" d="M618 368l-414-415q-11-10-25-10t-25 10l-93 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l93 93q10 11 25 11t25-11l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" />
|
|
||||||
|
|
||||||
<glyph glyph-name="left-open" unicode="" d="M654 689l-297-296 297-297q10-10 10-25t-10-25l-93-93q-11-10-25-10t-25 10l-414 415q-11 10-11 25t11 25l414 414q10 11 25 11t25-11l93-93q10-10 10-25t-10-25z" horiz-adv-x="714.3" />
|
|
||||||
|
|
||||||
<glyph glyph-name="up-open" unicode="" d="M939 114l-92-92q-11-10-26-10t-25 10l-296 297-296-297q-11-10-25-10t-25 10l-93 92q-11 11-11 26t11 25l414 414q11 10 25 10t25-10l414-414q11-11 11-25t-11-26z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="bell-ringing-o" unicode="" d="M498 857c-30 0-54-24-54-53 0-8 2-15 5-22-147-22-236-138-236-245 0-268-95-393-177-462 0-39 32-71 71-71h249c0-79 63-143 142-143s142 64 142 143h249c39 0 71 32 71 71-82 69-178 194-178 462 0 107-88 223-235 245 2 7 4 14 4 22 0 29-24 53-53 53z m-309-45c-81-74-118-170-118-275l71 0c0 89 28 162 95 223l-48 52z m617 0l-48-52c67-61 96-134 95-223l71 0c1 105-37 201-118 275z m-397-799c5 0 9-4 9-9 0-44 36-80 80-80 5 0 9-4 9-9s-4-9-9-9c-54 0-98 44-98 98 0 5 4 9 9 9z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="lock" unicode="" d="M179 428h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" />
|
|
||||||
|
|
||||||
<glyph glyph-name="globe" unicode="" d="M429 786q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m153-291q-2-1-6-5t-7-6q1 0 2 3t3 6 2 4q3 4 12 8 8 4 29 7 19 5 29-6-1 1 5 7t8 7q2 1 8 3t9 4l1 12q-7-1-10 4t-3 12q0-2-4-5 0 4-2 5t-7-1-5-1q-5 2-8 5t-5 9-2 8q-1 3-5 6t-5 6q-1 1-2 3t-1 4-3 3-3 1-4-3-4-5-2-3q-2 1-4 1t-2-1-3-1-3-2q-1-2-4-2t-5-1q8 3-1 6-5 2-9 2 6 2 5 6t-5 8h3q-1 2-5 5t-10 5-7 3q-5 3-19 5t-18 1q-3-4-3-6t2-8 2-7q1-3-3-7t-3-7q0-4 7-9t6-12q-2-4-9-9t-9-6q-3-5-1-11t6-9q1-1 1-2t-2-3-3-2-4-2l-1-1q-7-3-12 3t-7 15q-4 14-9 17-13 4-16-1-3 7-23 15-14 5-33 2 4 0 0 8-4 9-10 7 1 3 2 10t0 7q2 8 7 13 1 1 4 5t5 7 1 4q19-3 28 6 2 3 6 9t6 10q5 3 8 3t8-3 8-3q8-1 8 6t-4 11q7 0 2 10-2 4-5 5-6 2-15-3-4-2 2-4-1 0-6-6t-9-10-9 3q0 0-3 7t-5 8q-5 0-9-9 1 5-6 9t-14 4q11 7-4 15-4 3-12 3t-11-2q-2-4-3-7t3-4 6-3 6-2 5-2q8-6 5-8-1 0-5-2t-6-2-4-2q-1-3 0-8t-1-8q-3 3-5 10t-4 9q4-5-14-3l-5 0q-3 0-9-1t-12-1-7 5q-3 4 0 11 0 2 2 1-2 2-6 5t-6 5q-25-8-52-23 3 0 6 1 3 1 8 4t5 3q19 7 24 4l3 2q7-9 11-14-4 3-17 1-11-3-12-7 4-6 2-10-2 2-6 6t-8 6-8 3q-9 0-13-1-81-45-131-124 4-4 7-4 2-1 3-5t1-6 6 1q5-4 2-10 1 0 25-15 10-10 11-12 2-6-5-10-1 1-5 5t-5 2q-2-3 0-10t6-7q-4 0-5-9t-2-20 0-13l1-1q-2-6 3-19t12-11q-7-1 11-24 3-4 4-5 2-1 7-4t9-6 5-5q2-3 6-13t8-13q-2-3 5-11t6-13q-1 0-2-1t-1 0q2-4 9-8t8-7q1-2 1-6t2-6 4-1q2 11-13 35-8 13-9 16-2 2-4 8t-2 8q1 0 3 0t5-2 4-3 1-1q-1-4 1-10t7-10 10-11 6-7q4-4 8-11t0-8q5 0 11-5t10-11q3-5 4-15t3-13q1-4 5-8t7-5l9-5t7-3q3-2 10-6t12-7q6-2 9-2t8 1 8 2q8 1 16-8t12-12q20-10 30-6-1 0 1-4t4-9 5-8 3-5q3-3 10-8t10-8q4 2 4 5-1-5 4-11t10-6q8 2 8 18-17-8-27 10 0 0-2 3t-2 5-1 4 0 5 2 1q5 0 6 2t-1 7-2 8q-1 4-6 11t-7 8q-3-5-9-4t-9 5q0-1-1-3t-1-4q-7 0-8 0 1 2 1 10t2 13q1 2 3 6t5 9 2 7-3 5-9 1q-11 0-15-11-1-2-2-6t-2-6-5-4q-4-2-14-1t-13 3q-8 4-13 16t-5 20q0 6 1 15t2 14-3 14q2 1 5 5t5 6q2 1 3 1t3 0 2 1 1 3q0 1-2 2-1 1-2 1 4-1 16 1t15-1q9-6 12 1 0 1-1 6t0 7q3-15 16-5 2-1 9-3t9-2q2-1 4-3t3-3 3 0 5 4q5-8 7-13 6-23 10-25 4-2 6-1t3 5 0 8-1 7l-1 5v10l0 4q-8 2-10 7t0 10 9 10q0 1 4 2t9 4 7 4q12 11 8 20 4 0 6 5 0 0-2 2t-5 2-2 2q5 2 1 8 3 2 4 7t4 5q5-6 12-1 5 5 1 9 2 4 11 6t10 5q4-1 5 1t0 7 2 7q2 2 9 5t7 2l9 7q2 2 0 2 10-1 18 6 5 6-4 11 2 4-1 5t-9 4q2 0 7 0t5 1q9 5-3 9-10 2-24-7z m-91-490q115 21 195 106-1 2-7 2t-7 2q-10 4-13 5 1 4-1 7t-5 5-7 5-6 4q-1 1-4 3t-4 3-4 2-5 2-5-1l-2-1q-2 0-3-1t-3-2-2-1 0-2q-12 10-20 13-3 0-6 3t-6 4-6 0-6-3q-3-3-4-9t-1-7q-4 3 0 10t1 10q-1 3-6 2t-6-2-7-5-5-3-4-3-5-5q-2-2-4-6t-2-6q-1 2-7 3t-5 3q1-5 2-19t3-22q4-17-7-26-15-14-16-23-2-12 7-14 0-4-5-12t-4-12q0-3 2-9z" horiz-adv-x="857.1" />
|
|
||||||
|
|
||||||
<glyph glyph-name="brush" unicode="" d="M464 209q0-124-87-212t-210-87q-81 0-149 40 68 39 109 108t40 151q0 61 44 105t105 44 105-44 43-105z m415 562q32-32 32-79t-33-79l-318-318q-20 55-61 97t-97 62l318 318q32 32 79 32t80-33z" horiz-adv-x="928" />
|
|
||||||
|
|
||||||
<glyph glyph-name="attention" unicode="" d="M571 90v106q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-106q0-8 5-13t12-6h108q7 0 12 6t5 13z m-1 208l10 257q0 6-5 10-7 6-14 6h-122q-6 0-14-6-5-4-5-12l9-255q0-5 6-9t13-3h103q8 0 14 3t5 9z m-7 522l428-786q20-35-1-70-9-17-26-26t-35-10h-858q-18 0-35 10t-26 26q-21 35-1 70l429 786q9 17 26 27t36 10 36-10 27-27z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="plus" unicode="" d="M786 446v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q23 0 38-16t16-38z" horiz-adv-x="785.7" />
|
|
||||||
|
|
||||||
<glyph glyph-name="adjust" unicode="" d="M429 53v608q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41z m428 304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
|
|
||||||
|
|
||||||
<glyph glyph-name="edit" unicode="" d="M496 196l64 65-85 85-64-65v-31h53v-54h32z m245 402q-9 9-18 0l-196-196q-9-9 0-18t18 0l196 196q9 9 0 18z m45-331v-106q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-8-8-18-4-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v70q0 7 5 12l36 36q8 8 20 4t11-16z m-54 411l161-160-375-375h-161v160z m248-73l-51-52-161 161 51 52q16 15 38 15t38-15l85-85q16-16 16-38t-16-38z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="pencil" unicode="" d="M203 0l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" />
|
|
||||||
|
|
||||||
<glyph glyph-name="pin" unicode="" d="M268 375v250q0 8-5 13t-13 5-13-5-5-13v-250q0-8 5-13t13-5 13 5 5 13z m375-197q0-14-11-25t-25-10h-239l-29-270q-1-7-6-11t-11-5h-1q-15 0-17 15l-43 271h-225q-15 0-25 10t-11 25q0 69 44 124t99 55v286q-29 0-50 21t-22 50 22 50 50 22h357q29 0 50-22t21-50-21-50-50-21v-286q55 0 99-55t44-124z" horiz-adv-x="642.9" />
|
|
||||||
|
|
||||||
<glyph glyph-name="wrench" unicode="" d="M214 36q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m354 243q0-22-13-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t5-14z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="verified" unicode="" d="M926 453l-19 13c-21 14-30 41-23 65l6 22c10 34-13 69-48 75l-23 4c-25 4-45 23-49 48l-4 23c-6 35-41 57-75 47l-22-7c-24-7-51 2-65 22l-14 20c-21 29-62 33-88 9l-17-16c-19-17-46-21-69-8l-20 11c-31 17-70 3-84-30l-9-22c-9-24-33-39-58-37l-23 1c-36 2-65-28-62-63l2-23c2-25-13-49-36-59l-21-9c-33-14-46-53-29-84l12-20c13-22 10-50-7-69l-15-17c-24-27-19-68 11-88l19-13c21-14 30-41 23-65l-9-23c-10-34 13-69 48-75l23-4c25-4 45-23 49-48l4-23c6-35 41-57 75-47l22 7c24 7 51-2 65-22l14-19c21-29 62-33 88-9l17 16c19 17 46 21 69 8l20-11c31-17 70-3 84 30l9 22c9 24 33 39 58 37l23-1c36-2 65 28 62 63l-1 23c-2 25 13 49 36 59l21 9c33 14 46 53 29 84l-12 20c-13 22-10 50 7 69l15 17c25 26 20 68-9 88z m-399-189l-82-81-81 82-78 79 82 81 78-79 187 186 81-82-187-186z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="chart-bar" unicode="" d="M357 357v-286h-143v286h143z m214 286v-572h-142v572h142z m572-643v-72h-1143v858h71v-786h1072z m-357 500v-429h-143v429h143z m214 214v-643h-143v643h143z" horiz-adv-x="1142.9" />
|
|
||||||
|
|
||||||
<glyph glyph-name="users" unicode="" d="M331 357q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-355q0-67-41-106t-108-39h-488q-68 0-108 39t-41 106q0 29 2 57t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 24 12q34 0 62-11t47-30 35-45 24-54 15-61 8-61 2-57z m-572 712q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" />
|
|
||||||
|
|
||||||
<glyph glyph-name="address-book" unicode="" d="M0-143l0 1000 1000 0 0-178-109 0 0-95 109 0 0-178-109 0 0-98 109 0 0-177-109 0 0-96 109 0 0-178-1000 0z m193 285l504 0 0 155-187 111q37 19 58 54t22 77q0 58-42 101t-103 43-102-43-42-101q0-43 22-77t58-54l-188-111 0-155z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="cog-alt" unicode="" d="M500 357q0 59-42 101t-101 42-101-42-42-101 42-101 101-42 101 42 42 101z m429-286q0 29-22 51t-50 21-50-21-21-51q0-29 21-50t50-21 51 21 21 50z m0 572q0 29-22 50t-50 21-50-21-21-50q0-30 21-51t50-21 51 21 21 51z m-215-235v-103q0-6-4-11t-8-6l-87-14q-6-19-18-42 19-27 50-64 4-6 4-11 0-7-4-11-12-17-46-50t-43-33q-7 0-12 4l-64 50q-21-11-43-17-6-60-13-87-4-13-17-13h-104q-6 0-11 4t-5 10l-13 85q-19 6-42 18l-66-50q-4-4-11-4-6 0-12 4-80 75-80 90 0 5 4 10 5 8 23 30t26 34q-13 24-20 46l-85 13q-5 1-9 5t-4 11v104q0 5 4 10t9 6l86 14q7 19 18 42-19 27-50 64-4 6-4 11 0 7 4 12 12 16 46 49t44 33q6 0 12-4l64-50q19 10 43 18 6 60 13 86 3 13 16 13h104q6 0 11-4t6-10l13-85q19-6 42-17l65 49q5 4 12 4 6 0 11-4 81-75 81-90 0-4-4-10-7-9-24-30t-25-34q13-27 19-46l85-12q6-2 9-6t4-11z m357-298v-78q0-9-83-17-6-15-16-29 28-63 28-77 0-2-2-4-68-40-69-40-5 0-26 27t-29 37q-11-1-17-1t-17 1q-7-11-29-37t-25-27q-1 0-69 40-3 2-3 4 0 14 29 77-10 14-17 29-83 8-83 17v78q0 9 83 18 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-38q12 1 17 1t17-1q28 40 51 63l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-9 83-18z m0 572v-78q0-9-83-18-6-15-16-29 28-63 28-77 0-2-2-4-68-39-69-39-5 0-26 26t-29 38q-11-1-17-1t-17 1q-7-12-29-38t-25-26q-1 0-69 39-3 2-3 4 0 14 29 77-10 14-17 29-83 9-83 18v78q0 9 83 17 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-37q12 1 17 1t17-1q28 39 51 62l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-8 83-17z" horiz-adv-x="1071.4" />
|
|
||||||
|
|
||||||
<glyph glyph-name="zoom-in" unicode="" d="M571 411v-36q0-7-5-13t-12-5h-125v-125q0-7-6-13t-12-5h-36q-7 0-13 5t-5 13v125h-125q-7 0-12 5t-6 13v36q0 7 6 12t12 5h125v125q0 8 5 13t13 5h36q7 0 12-5t6-13v-125h125q7 0 12-5t5-12z m72-18q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-21-50t-51-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="home-2" unicode="" d="M521 826q322-279 500-429 20-16 20-40 0-21-15-37t-36-15l-105 0 0-364q0-21-15-37t-36-16l-156 0q-22 0-37 16t-16 37l0 208-209 0 0-208q0-21-15-37t-36-16l-156 0q-21 0-37 16t-16 37l0 364-103 0q-22 0-37 15t-16 37 19 40z" horiz-adv-x="1041" />
|
|
||||||
|
|
||||||
<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="link-ext" unicode="" d="M786 339v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<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="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="gauge" unicode="" d="M214 214q0 30-21 51t-50 21-51-21-21-51 21-50 51-21 50 21 21 50z m107 250q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m239-268l57 213q3 14-5 27t-21 16-27-3-17-22l-56-213q-33-3-60-25t-35-55q-11-43 11-81t66-50 81 11 50 66q9 33-4 65t-40 51z m369 18q0 30-21 51t-51 21-50-21-21-51 21-50 50-21 51 21 21 50z m-358 357q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m250-107q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m179-250q0-145-79-269-10-17-30-17h-782q-20 0-30 17-79 123-79 269 0 102 40 194t106 160 160 107 194 39 194-39 160-107 106-160 40-194z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="comment-empty" unicode="" d="M500 643q-114 0-213-39t-157-105-59-142q0-62 40-119t113-98l48-28-15-53q-13-51-39-97 85 36 154 96l24 21 32-3q38-5 72-5 114 0 213 39t157 105 59 142-59 142-157 105-213 39z m500-286q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12h-3q-8 0-15 6t-9 15v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 97 67 179t182 130 251 48 251-48 182-130 67-179z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="bell-alt" unicode="" d="M509-89q0 8-9 8-33 0-57 24t-23 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m455 160q0-29-21-50t-50-21h-250q0-59-42-101t-101-42-101 42-42 101h-250q-29 0-50 21t-21 50q28 24 51 49t47 67 42 89 27 115 11 145q0 84 66 157t171 89q-5 10-5 21 0 23 16 38t38 16 38-16 16-38q0-11-5-21 106-16 171-89t66-157q0-78 11-145t28-115 41-89 48-67 50-49z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="plus-squared" unicode="" d="M714 321v72q0 14-10 25t-25 10h-179v179q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-179h-178q-15 0-25-10t-11-25v-72q0-14 11-25t25-10h178v-179q0-14 11-25t25-11h71q15 0 25 11t11 25v179h179q14 0 25 10t10 25z m143 304v-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="laptop" unicode="" d="M232 143q-37 0-63 26t-26 63v393q0 37 26 63t63 26h607q37 0 63-26t27-63v-393q0-37-27-63t-63-26h-607z m-18 482v-393q0-7 6-13t12-5h607q8 0 13 5t5 13v393q0 7-5 12t-13 6h-607q-7 0-12-6t-6-12z m768-518h89v-54q0-22-26-37t-63-16h-893q-36 0-63 16t-26 37v54h982z m-402-54q9 0 9 9t-9 9h-89q-9 0-9-9t9-9h89z" horiz-adv-x="1071.4" />
|
|
||||||
|
|
||||||
<glyph glyph-name="quote-right" unicode="" d="M429 678v-392q0-58-23-111t-61-91-91-61-111-23h-36q-14 0-25 11t-11 25v71q0 15 11 25t25 11h36q59 0 101 42t42 101v17q0 23-16 38t-38 16h-125q-44 0-76 31t-31 76v214q0 45 31 76t76 32h214q45 0 76-32t32-76z m500 0v-392q0-58-23-111t-61-91-91-61-111-23h-36q-14 0-25 11t-11 25v71q0 15 11 25t25 11h36q59 0 101 42t42 101v17q0 23-16 38t-38 16h-125q-44 0-76 31t-31 76v214q0 45 31 76t76 32h214q45 0 76-32t32-76z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="reply" unicode="" d="M1000 232q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="smile" unicode="" d="M633 257q-21-67-77-109t-127-41-128 41-77 109q-4 14 3 27t21 18q14 4 27-2t17-22q14-44 52-72t85-28 84 28 52 72q4 15 18 22t27 2 21-18 2-27z m-276 243q0-30-21-51t-50-21-51 21-21 51 21 50 51 21 50-21 21-50z m286 0q0-30-21-51t-51-21-50 21-21 51 21 50 50 21 51-21 21-50z m143-143q0 73-29 139t-76 114-114 76-138 28-139-28-114-76-76-114-29-139 29-139 76-113 114-77 139-28 138 28 114 77 76 113 29 139z m71 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
|
|
||||||
|
|
||||||
<glyph glyph-name="lock-open-alt" unicode="" d="M589 428q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-14-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" />
|
|
||||||
|
|
||||||
<glyph glyph-name="ellipsis" unicode="" d="M214 446v-107q0-22-15-38t-38-15h-107q-23 0-38 15t-16 38v107q0 23 16 38t38 16h107q22 0 38-16t15-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-15 38v107q0 23 15 38t38 16h107q23 0 38-16t16-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-16 38v107q0 23 16 38t38 16h107q23 0 38-16t16-38z" horiz-adv-x="785.7" />
|
|
||||||
|
|
||||||
<glyph glyph-name="play-circled" unicode="" d="M429 786q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m214-460q18 10 18 31t-18 31l-304 178q-17 11-35 1-18-11-18-31v-358q0-20 18-31 9-4 17-4 10 0 18 5z" horiz-adv-x="857.1" />
|
|
||||||
|
|
||||||
<glyph glyph-name="thumbs-up-alt" unicode="" d="M143 107q0 15-11 25t-25 11q-15 0-25-11t-11-25q0-15 11-25t25-11q15 0 25 11t11 25z m89 286v-357q0-15-10-25t-26-11h-160q-15 0-25 11t-11 25v357q0 14 11 25t25 10h160q15 0 26-10t10-25z m661 0q0-48-31-83 9-25 9-43 1-42-24-76 9-31 0-66-9-31-31-52 5-62-27-101-36-43-110-44h-72q-37 0-80 9t-68 16-67 22q-69 24-88 25-15 0-25 11t-11 25v357q0 14 10 25t24 11q13 1 42 33t57 67q38 49 56 67 10 10 17 27t10 27 8 34q4 22 7 34t11 29 19 28q10 11 25 11 25 0 46-6t33-15 22-22 14-25 7-28 2-25 1-22q0-21-6-43t-10-33-16-31q-1-4-5-10t-6-13-5-13h155q43 0 75-32t32-75z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="apple" unicode="" d="M777 179q-21-70-68-139-72-110-144-110-27 0-78 18-48 18-84 18-34 0-79-19-45-19-74-19-85 0-168 145-82 146-82 281 0 127 63 208 63 81 159 81 40 0 98-17 58-17 77-17 25 0 80 19 57 19 97 19 66 0 119-36 29-20 58-56-44-37-64-66-36-52-36-115 0-69 38-125t88-70z m-209 655q0-34-17-76-16-42-52-77-30-30-60-40-20-7-58-10 2 83 44 143 41 60 139 83 1-2 2-6t1-6q0-2 0-6t1-5z" horiz-adv-x="785.7" />
|
|
||||||
|
|
||||||
<glyph glyph-name="android" unicode="" d="M275 588q9 0 16 6t6 15-6 16-16 6-15-6-6-16 6-15 15-6z m236 0q9 0 15 6t6 15-6 16-15 6-16-6-6-16 6-15 16-6z m-453-103q23 0 40-17t16-40v-240q0-24-16-41t-40-17-41 17-17 41v240q0 23 17 40t41 17z m591-11v-371q0-26-18-44t-43-18h-42v-127q0-24-16-40t-41-17-41 17-17 40v127h-77v-127q0-24-16-40t-41-17q-24 0-40 17t-17 40l-1 127h-41q-26 0-43 18t-18 44v371h512z m-129 226q59-30 95-85t36-121h-516q0 66 35 121t96 85l-39 73q-4 8 2 12 8 3 12-4l40-74q53 24 112 24t112-24l40 74q4 7 11 4 7-4 3-12z m266-272v-240q0-24-17-41t-41-17q-23 0-40 17t-17 41v240q0 24 17 40t40 17q24 0 41-17t17-40z" horiz-adv-x="785.7" />
|
|
||||||
|
|
||||||
<glyph glyph-name="paper-plane-empty" unicode="" d="M984 851q19-13 15-36l-142-857q-3-16-18-25-8-5-18-5-6 0-13 3l-294 120-166-182q-10-12-27-12-7 0-12 2-11 4-17 13t-6 21v252l-264 108q-20 8-22 30-2 22 18 33l928 536q20 12 38-1z m-190-837l123 739-800-462 187-76 482 356-267-444z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="binoculars" unicode="" d="M393 678v-428q0-15-11-25t-25-11v-321q0-15-10-25t-26-11h-285q-15 0-25 11t-11 25v285l139 488q4 12 17 12h237z m178 0v-392h-142v392h142z m429-500v-285q0-15-11-25t-25-11h-285q-15 0-25 11t-11 25v321q-15 0-25 11t-11 25v428h237q13 0 17-12z m-589 661v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z m375 0v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="user-plus" unicode="" d="M393 357q-89 0-152 63t-62 151 62 152 152 63 151-63 63-152-63-151-151-63z m536-71h196q7 0 13-6t5-12v-107q0-8-5-13t-13-5h-196v-197q0-7-6-12t-12-6h-107q-8 0-13 6t-5 12v197h-197q-7 0-12 5t-6 13v107q0 7 6 12t12 6h197v196q0 7 5 13t13 5h107q7 0 12-5t6-13v-196z m-411-125q0-29 21-51t50-21h143v-133q-38-28-95-28h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q11 0 22-10 44-34 86-51t92-17 92 17 86 51q11 10 22 10 73 0 121-54h-125q-29 0-50-21t-21-50v-107z" horiz-adv-x="1142.9" />
|
|
||||||
|
|
||||||
<glyph glyph-name="hashtag" unicode="" d="M553 286l36 142h-142l-36-142h142z m429 281l-32-125q-4-14-17-14h-182l-36-142h173q9 0 14-7 6-8 4-16l-32-125q-2-13-17-13h-182l-45-183q-4-14-18-14h-125q-9 0-14 7-5 7-4 16l44 174h-142l-45-183q-4-14-17-14h-126q-8 0-14 7-5 7-3 16l43 174h-173q-9 0-14 7-5 6-4 15l32 125q4 14 17 14h182l36 142h-173q-9 0-14 7-6 8-4 16l32 125q2 13 17 13h182l46 183q3 14 17 14h125q9 0 14-7 5-7 4-16l-44-174h142l45 183q4 14 18 14h125q8 0 14-7 5-7 3-16l-43-174h173q9 0 14-7 5-6 4-15z" horiz-adv-x="1000" />
|
|
||||||
</font>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 28 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,126 +0,0 @@
|
||||||
<?xml version="1.0" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<metadata>Copyright (C) 2019 by original authors @ fontello.com</metadata>
|
|
||||||
<defs>
|
|
||||||
<font id="fontello" horiz-adv-x="1000" >
|
|
||||||
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="857" descent="-143" />
|
|
||||||
<missing-glyph horiz-adv-x="1000" />
|
|
||||||
<glyph glyph-name="cancel" unicode="" d="M724 119q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
|
|
||||||
|
|
||||||
<glyph glyph-name="upload" unicode="" d="M714 36q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m143 0q0 14-10 25t-26 10-25-10-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-38t-38-16h-821q-23 0-38 16t-16 38v179q0 22 16 38t38 15h238q12-31 39-51t62-20h143q34 0 61 20t40 51h238q22 0 38-15t16-38z m-182 361q-9-22-33-22h-143v-250q0-15-10-25t-25-11h-143q-15 0-25 11t-11 25v250h-143q-23 0-33 22-9 22 8 39l250 250q10 10 25 10t25-10l250-250q18-17 8-39z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="star" unicode="" d="M929 496q0-12-15-27l-202-197 48-279q0-4 0-12 0-11-6-19t-17-9q-10 0-22 7l-251 132-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="star-empty" unicode="" d="M635 297l170 166-235 34-106 213-105-213-236-34 171-166-41-235 211 111 211-111z m294 199q0-12-15-27l-202-197 48-279q0-4 0-12 0-28-23-28-10 0-22 7l-251 132-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="retweet" unicode="" d="M714 18q0-7-5-13t-13-5h-535q-5 0-8 1t-5 4-3 4-2 7 0 6v335h-107q-15 0-25 11t-11 25q0 13 8 23l179 214q11 12 27 12t28-12l178-214q9-10 9-23 0-15-11-25t-25-11h-107v-214h321q9 0 14-6l89-108q4-5 4-11z m357 232q0-13-8-23l-178-214q-12-13-28-13t-27 13l-179 214q-8 10-8 23 0 14 11 25t25 11h107v214h-322q-9 0-14 7l-89 107q-4 5-4 11 0 7 5 12t13 6h536q4 0 7-1t5-4 3-5 2-6 1-7v-334h107q14 0 25-11t10-25z" horiz-adv-x="1071.4" />
|
|
||||||
|
|
||||||
<glyph glyph-name="eye-off" unicode="" d="M310 112l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-91l157 280q4-25 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-85t81-103q11-19 11-39z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="search" unicode="" d="M643 393q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-22-50t-50-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="cog" unicode="" d="M571 357q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 16 20 16h124q7 0 13-5t7-12l15-103q28-9 51-20l79 59q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-12 0-7-4-13-9-12-29-37t-30-40q15-28 23-54l102-16q7-1 12-7t4-13z" horiz-adv-x="857.1" />
|
|
||||||
|
|
||||||
<glyph glyph-name="logout" unicode="" d="M357 53q0-2 1-11t0-14-2-14-5-10-12-4h-178q-67 0-114 47t-47 114v392q0 67 47 114t114 47h178q8 0 13-5t5-13q0-2 1-11t0-15-2-13-5-11-12-3h-178q-37 0-63-26t-27-64v-392q0-37 27-63t63-27h174t6 0 7-2 4-3 4-5 1-8z m518 304q0-14-11-25l-303-304q-11-10-25-10t-25 10-11 25v161h-250q-14 0-25 11t-11 25v214q0 15 11 25t25 11h250v161q0 14 11 25t25 10 25-10l303-304q11-10 11-25z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="down-open" unicode="" d="M939 406l-414-413q-10-11-25-11t-25 11l-414 413q-11 11-11 26t11 25l93 92q10 11 25 11t25-11l296-296 296 296q11 11 25 11t26-11l92-92q11-11 11-25t-11-26z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="attach" unicode="" d="M244-133q-102 0-170 72-72 70-74 166t84 190l496 496q80 80 174 54 44-12 79-47t47-79q26-96-54-176l-474-474q-40-40-88-46-48-4-80 28-30 24-27 74t47 92l332 334q24 26 50 0t0-50l-332-332q-44-44-20-70 12-8 24-6 24 4 46 26l474 474q50 50 34 108-16 60-76 76-54 14-108-36l-494-494q-66-76-64-143t52-117q50-48 117-50t141 62l496 494q24 24 50 0 26-22 0-48l-496-496q-82-82-186-82z" horiz-adv-x="939" />
|
|
||||||
|
|
||||||
<glyph glyph-name="picture" unicode="" d="M357 536q0-45-31-76t-76-32-76 32-31 76 31 76 76 31 76-31 31-76z m572-215v-250h-786v107l178 179 90-89 285 285z m53 393h-893q-7 0-12-5t-6-13v-678q0-7 6-13t12-5h893q7 0 13 5t5 13v678q0 8-5 13t-13 5z m89-18v-678q0-37-26-63t-63-27h-893q-36 0-63 27t-26 63v678q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" />
|
|
||||||
|
|
||||||
<glyph glyph-name="video" unicode="" d="M214-36v72q0 14-10 25t-25 10h-72q-14 0-25-10t-11-25v-72q0-14 11-25t25-11h72q14 0 25 11t10 25z m0 214v72q0 14-10 25t-25 11h-72q-14 0-25-11t-11-25v-72q0-14 11-25t25-10h72q14 0 25 10t10 25z m0 215v71q0 15-10 25t-25 11h-72q-14 0-25-11t-11-25v-71q0-15 11-25t25-11h72q14 0 25 11t10 25z m572-429v286q0 14-11 25t-25 11h-429q-14 0-25-11t-10-25v-286q0-14 10-25t25-11h429q15 0 25 11t11 25z m-572 643v71q0 15-10 26t-25 10h-72q-14 0-25-10t-11-26v-71q0-14 11-25t25-11h72q14 0 25 11t10 25z m786-643v72q0 14-11 25t-25 10h-71q-15 0-25-10t-11-25v-72q0-14 11-25t25-11h71q15 0 25 11t11 25z m-214 429v285q0 15-11 26t-25 10h-429q-14 0-25-10t-10-26v-285q0-15 10-25t25-11h429q15 0 25 11t11 25z m214-215v72q0 14-11 25t-25 11h-71q-15 0-25-11t-11-25v-72q0-14 11-25t25-10h71q15 0 25 10t11 25z m0 215v71q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-71q0-15 11-25t25-11h71q15 0 25 11t11 25z m0 214v71q0 15-11 26t-25 10h-71q-15 0-25-10t-11-26v-71q0-14 11-25t25-11h71q15 0 25 11t11 25z m71 89v-750q0-37-26-63t-63-26h-893q-36 0-63 26t-26 63v750q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" />
|
|
||||||
|
|
||||||
<glyph glyph-name="right-open" unicode="" d="M618 368l-414-415q-11-10-25-10t-25 10l-93 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l93 93q10 11 25 11t25-11l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" />
|
|
||||||
|
|
||||||
<glyph glyph-name="left-open" unicode="" d="M654 689l-297-296 297-297q10-10 10-25t-10-25l-93-93q-11-10-25-10t-25 10l-414 415q-11 10-11 25t11 25l414 414q10 11 25 11t25-11l93-93q10-10 10-25t-10-25z" horiz-adv-x="714.3" />
|
|
||||||
|
|
||||||
<glyph glyph-name="up-open" unicode="" d="M939 114l-92-92q-11-10-26-10t-25 10l-296 297-296-297q-11-10-25-10t-25 10l-93 92q-11 11-11 26t11 25l414 414q11 10 25 10t25-10l414-414q11-11 11-25t-11-26z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="bell-ringing-o" unicode="" d="M498 857c-30 0-54-24-54-53 0-8 2-15 5-22-147-22-236-138-236-245 0-268-95-393-177-462 0-39 32-71 71-71h249c0-79 63-143 142-143s142 64 142 143h249c39 0 71 32 71 71-82 69-178 194-178 462 0 107-88 223-235 245 2 7 4 14 4 22 0 29-24 53-53 53z m-309-45c-81-74-118-170-118-275l71 0c0 89 28 162 95 223l-48 52z m617 0l-48-52c67-61 96-134 95-223l71 0c1 105-37 201-118 275z m-397-799c5 0 9-4 9-9 0-44 36-80 80-80 5 0 9-4 9-9s-4-9-9-9c-54 0-98 44-98 98 0 5 4 9 9 9z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="lock" unicode="" d="M179 428h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" />
|
|
||||||
|
|
||||||
<glyph glyph-name="globe" unicode="" d="M429 786q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m153-291q-2-1-6-5t-7-6q1 0 2 3t3 6 2 4q3 4 12 8 8 4 29 7 19 5 29-6-1 1 5 7t8 7q2 1 8 3t9 4l1 12q-7-1-10 4t-3 12q0-2-4-5 0 4-2 5t-7-1-5-1q-5 2-8 5t-5 9-2 8q-1 3-5 6t-5 6q-1 1-2 3t-1 4-3 3-3 1-4-3-4-5-2-3q-2 1-4 1t-2-1-3-1-3-2q-1-2-4-2t-5-1q8 3-1 6-5 2-9 2 6 2 5 6t-5 8h3q-1 2-5 5t-10 5-7 3q-5 3-19 5t-18 1q-3-4-3-6t2-8 2-7q1-3-3-7t-3-7q0-4 7-9t6-12q-2-4-9-9t-9-6q-3-5-1-11t6-9q1-1 1-2t-2-3-3-2-4-2l-1-1q-7-3-12 3t-7 15q-4 14-9 17-13 4-16-1-3 7-23 15-14 5-33 2 4 0 0 8-4 9-10 7 1 3 2 10t0 7q2 8 7 13 1 1 4 5t5 7 1 4q19-3 28 6 2 3 6 9t6 10q5 3 8 3t8-3 8-3q8-1 8 6t-4 11q7 0 2 10-2 4-5 5-6 2-15-3-4-2 2-4-1 0-6-6t-9-10-9 3q0 0-3 7t-5 8q-5 0-9-9 1 5-6 9t-14 4q11 7-4 15-4 3-12 3t-11-2q-2-4-3-7t3-4 6-3 6-2 5-2q8-6 5-8-1 0-5-2t-6-2-4-2q-1-3 0-8t-1-8q-3 3-5 10t-4 9q4-5-14-3l-5 0q-3 0-9-1t-12-1-7 5q-3 4 0 11 0 2 2 1-2 2-6 5t-6 5q-25-8-52-23 3 0 6 1 3 1 8 4t5 3q19 7 24 4l3 2q7-9 11-14-4 3-17 1-11-3-12-7 4-6 2-10-2 2-6 6t-8 6-8 3q-9 0-13-1-81-45-131-124 4-4 7-4 2-1 3-5t1-6 6 1q5-4 2-10 1 0 25-15 10-10 11-12 2-6-5-10-1 1-5 5t-5 2q-2-3 0-10t6-7q-4 0-5-9t-2-20 0-13l1-1q-2-6 3-19t12-11q-7-1 11-24 3-4 4-5 2-1 7-4t9-6 5-5q2-3 6-13t8-13q-2-3 5-11t6-13q-1 0-2-1t-1 0q2-4 9-8t8-7q1-2 1-6t2-6 4-1q2 11-13 35-8 13-9 16-2 2-4 8t-2 8q1 0 3 0t5-2 4-3 1-1q-1-4 1-10t7-10 10-11 6-7q4-4 8-11t0-8q5 0 11-5t10-11q3-5 4-15t3-13q1-4 5-8t7-5l9-5t7-3q3-2 10-6t12-7q6-2 9-2t8 1 8 2q8 1 16-8t12-12q20-10 30-6-1 0 1-4t4-9 5-8 3-5q3-3 10-8t10-8q4 2 4 5-1-5 4-11t10-6q8 2 8 18-17-8-27 10 0 0-2 3t-2 5-1 4 0 5 2 1q5 0 6 2t-1 7-2 8q-1 4-6 11t-7 8q-3-5-9-4t-9 5q0-1-1-3t-1-4q-7 0-8 0 1 2 1 10t2 13q1 2 3 6t5 9 2 7-3 5-9 1q-11 0-15-11-1-2-2-6t-2-6-5-4q-4-2-14-1t-13 3q-8 4-13 16t-5 20q0 6 1 15t2 14-3 14q2 1 5 5t5 6q2 1 3 1t3 0 2 1 1 3q0 1-2 2-1 1-2 1 4-1 16 1t15-1q9-6 12 1 0 1-1 6t0 7q3-15 16-5 2-1 9-3t9-2q2-1 4-3t3-3 3 0 5 4q5-8 7-13 6-23 10-25 4-2 6-1t3 5 0 8-1 7l-1 5v10l0 4q-8 2-10 7t0 10 9 10q0 1 4 2t9 4 7 4q12 11 8 20 4 0 6 5 0 0-2 2t-5 2-2 2q5 2 1 8 3 2 4 7t4 5q5-6 12-1 5 5 1 9 2 4 11 6t10 5q4-1 5 1t0 7 2 7q2 2 9 5t7 2l9 7q2 2 0 2 10-1 18 6 5 6-4 11 2 4-1 5t-9 4q2 0 7 0t5 1q9 5-3 9-10 2-24-7z m-91-490q115 21 195 106-1 2-7 2t-7 2q-10 4-13 5 1 4-1 7t-5 5-7 5-6 4q-1 1-4 3t-4 3-4 2-5 2-5-1l-2-1q-2 0-3-1t-3-2-2-1 0-2q-12 10-20 13-3 0-6 3t-6 4-6 0-6-3q-3-3-4-9t-1-7q-4 3 0 10t1 10q-1 3-6 2t-6-2-7-5-5-3-4-3-5-5q-2-2-4-6t-2-6q-1 2-7 3t-5 3q1-5 2-19t3-22q4-17-7-26-15-14-16-23-2-12 7-14 0-4-5-12t-4-12q0-3 2-9z" horiz-adv-x="857.1" />
|
|
||||||
|
|
||||||
<glyph glyph-name="brush" unicode="" d="M464 209q0-124-87-212t-210-87q-81 0-149 40 68 39 109 108t40 151q0 61 44 105t105 44 105-44 43-105z m415 562q32-32 32-79t-33-79l-318-318q-20 55-61 97t-97 62l318 318q32 32 79 32t80-33z" horiz-adv-x="928" />
|
|
||||||
|
|
||||||
<glyph glyph-name="attention" unicode="" d="M571 90v106q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-106q0-8 5-13t12-6h108q7 0 12 6t5 13z m-1 208l10 257q0 6-5 10-7 6-14 6h-122q-6 0-14-6-5-4-5-12l9-255q0-5 6-9t13-3h103q8 0 14 3t5 9z m-7 522l428-786q20-35-1-70-9-17-26-26t-35-10h-858q-18 0-35 10t-26 26q-21 35-1 70l429 786q9 17 26 27t36 10 36-10 27-27z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="plus" unicode="" d="M786 446v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q23 0 38-16t16-38z" horiz-adv-x="785.7" />
|
|
||||||
|
|
||||||
<glyph glyph-name="adjust" unicode="" d="M429 53v608q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41z m428 304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
|
|
||||||
|
|
||||||
<glyph glyph-name="edit" unicode="" d="M496 196l64 65-85 85-64-65v-31h53v-54h32z m245 402q-9 9-18 0l-196-196q-9-9 0-18t18 0l196 196q9 9 0 18z m45-331v-106q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-8-8-18-4-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v70q0 7 5 12l36 36q8 8 20 4t11-16z m-54 411l161-160-375-375h-161v160z m248-73l-51-52-161 161 51 52q16 15 38 15t38-15l85-85q16-16 16-38t-16-38z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="pencil" unicode="" d="M203 0l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" />
|
|
||||||
|
|
||||||
<glyph glyph-name="pin" unicode="" d="M268 375v250q0 8-5 13t-13 5-13-5-5-13v-250q0-8 5-13t13-5 13 5 5 13z m375-197q0-14-11-25t-25-10h-239l-29-270q-1-7-6-11t-11-5h-1q-15 0-17 15l-43 271h-225q-15 0-25 10t-11 25q0 69 44 124t99 55v286q-29 0-50 21t-22 50 22 50 50 22h357q29 0 50-22t21-50-21-50-50-21v-286q55 0 99-55t44-124z" horiz-adv-x="642.9" />
|
|
||||||
|
|
||||||
<glyph glyph-name="wrench" unicode="" d="M214 36q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m354 243q0-22-13-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t5-14z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="verified" unicode="" d="M926 453l-19 13c-21 14-30 41-23 65l6 22c10 34-13 69-48 75l-23 4c-25 4-45 23-49 48l-4 23c-6 35-41 57-75 47l-22-7c-24-7-51 2-65 22l-14 20c-21 29-62 33-88 9l-17-16c-19-17-46-21-69-8l-20 11c-31 17-70 3-84-30l-9-22c-9-24-33-39-58-37l-23 1c-36 2-65-28-62-63l2-23c2-25-13-49-36-59l-21-9c-33-14-46-53-29-84l12-20c13-22 10-50-7-69l-15-17c-24-27-19-68 11-88l19-13c21-14 30-41 23-65l-9-23c-10-34 13-69 48-75l23-4c25-4 45-23 49-48l4-23c6-35 41-57 75-47l22 7c24 7 51-2 65-22l14-19c21-29 62-33 88-9l17 16c19 17 46 21 69 8l20-11c31-17 70-3 84 30l9 22c9 24 33 39 58 37l23-1c36-2 65 28 62 63l-1 23c-2 25 13 49 36 59l21 9c33 14 46 53 29 84l-12 20c-13 22-10 50 7 69l15 17c25 26 20 68-9 88z m-399-189l-82-81-81 82-78 79 82 81 78-79 187 186 81-82-187-186z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="chart-bar" unicode="" d="M357 357v-286h-143v286h143z m214 286v-572h-142v572h142z m572-643v-72h-1143v858h71v-786h1072z m-357 500v-429h-143v429h143z m214 214v-643h-143v643h143z" horiz-adv-x="1142.9" />
|
|
||||||
|
|
||||||
<glyph glyph-name="users" unicode="" d="M331 357q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-355q0-67-41-106t-108-39h-488q-68 0-108 39t-41 106q0 29 2 57t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 24 12q34 0 62-11t47-30 35-45 24-54 15-61 8-61 2-57z m-572 712q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" />
|
|
||||||
|
|
||||||
<glyph glyph-name="address-book" unicode="" d="M0-143l0 1000 1000 0 0-178-109 0 0-95 109 0 0-178-109 0 0-98 109 0 0-177-109 0 0-96 109 0 0-178-1000 0z m193 285l504 0 0 155-187 111q37 19 58 54t22 77q0 58-42 101t-103 43-102-43-42-101q0-43 22-77t58-54l-188-111 0-155z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="cog-alt" unicode="" d="M500 357q0 59-42 101t-101 42-101-42-42-101 42-101 101-42 101 42 42 101z m429-286q0 29-22 51t-50 21-50-21-21-51q0-29 21-50t50-21 51 21 21 50z m0 572q0 29-22 50t-50 21-50-21-21-50q0-30 21-51t50-21 51 21 21 51z m-215-235v-103q0-6-4-11t-8-6l-87-14q-6-19-18-42 19-27 50-64 4-6 4-11 0-7-4-11-12-17-46-50t-43-33q-7 0-12 4l-64 50q-21-11-43-17-6-60-13-87-4-13-17-13h-104q-6 0-11 4t-5 10l-13 85q-19 6-42 18l-66-50q-4-4-11-4-6 0-12 4-80 75-80 90 0 5 4 10 5 8 23 30t26 34q-13 24-20 46l-85 13q-5 1-9 5t-4 11v104q0 5 4 10t9 6l86 14q7 19 18 42-19 27-50 64-4 6-4 11 0 7 4 12 12 16 46 49t44 33q6 0 12-4l64-50q19 10 43 18 6 60 13 86 3 13 16 13h104q6 0 11-4t6-10l13-85q19-6 42-17l65 49q5 4 12 4 6 0 11-4 81-75 81-90 0-4-4-10-7-9-24-30t-25-34q13-27 19-46l85-12q6-2 9-6t4-11z m357-298v-78q0-9-83-17-6-15-16-29 28-63 28-77 0-2-2-4-68-40-69-40-5 0-26 27t-29 37q-11-1-17-1t-17 1q-7-11-29-37t-25-27q-1 0-69 40-3 2-3 4 0 14 29 77-10 14-17 29-83 8-83 17v78q0 9 83 18 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-38q12 1 17 1t17-1q28 40 51 63l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-9 83-18z m0 572v-78q0-9-83-18-6-15-16-29 28-63 28-77 0-2-2-4-68-39-69-39-5 0-26 26t-29 38q-11-1-17-1t-17 1q-7-12-29-38t-25-26q-1 0-69 39-3 2-3 4 0 14 29 77-10 14-17 29-83 9-83 18v78q0 9 83 17 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-37q12 1 17 1t17-1q28 39 51 62l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-8 83-17z" horiz-adv-x="1071.4" />
|
|
||||||
|
|
||||||
<glyph glyph-name="zoom-in" unicode="" d="M571 411v-36q0-7-5-13t-12-5h-125v-125q0-7-6-13t-12-5h-36q-7 0-13 5t-5 13v125h-125q-7 0-12 5t-6 13v36q0 7 6 12t12 5h125v125q0 8 5 13t13 5h36q7 0 12-5t6-13v-125h125q7 0 12-5t5-12z m72-18q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-21-50t-51-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="home-2" unicode="" d="M521 826q322-279 500-429 20-16 20-40 0-21-15-37t-36-15l-105 0 0-364q0-21-15-37t-36-16l-156 0q-22 0-37 16t-16 37l0 208-209 0 0-208q0-21-15-37t-36-16l-156 0q-21 0-37 16t-16 37l0 364-103 0q-22 0-37 15t-16 37 19 40z" horiz-adv-x="1041" />
|
|
||||||
|
|
||||||
<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="link-ext" unicode="" d="M786 339v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<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="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="gauge" unicode="" d="M214 214q0 30-21 51t-50 21-51-21-21-51 21-50 51-21 50 21 21 50z m107 250q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m239-268l57 213q3 14-5 27t-21 16-27-3-17-22l-56-213q-33-3-60-25t-35-55q-11-43 11-81t66-50 81 11 50 66q9 33-4 65t-40 51z m369 18q0 30-21 51t-51 21-50-21-21-51 21-50 50-21 51 21 21 50z m-358 357q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m250-107q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m179-250q0-145-79-269-10-17-30-17h-782q-20 0-30 17-79 123-79 269 0 102 40 194t106 160 160 107 194 39 194-39 160-107 106-160 40-194z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="comment-empty" unicode="" d="M500 643q-114 0-213-39t-157-105-59-142q0-62 40-119t113-98l48-28-15-53q-13-51-39-97 85 36 154 96l24 21 32-3q38-5 72-5 114 0 213 39t157 105 59 142-59 142-157 105-213 39z m500-286q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12h-3q-8 0-15 6t-9 15v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 97 67 179t182 130 251 48 251-48 182-130 67-179z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="bell-alt" unicode="" d="M509-89q0 8-9 8-33 0-57 24t-23 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m455 160q0-29-21-50t-50-21h-250q0-59-42-101t-101-42-101 42-42 101h-250q-29 0-50 21t-21 50q28 24 51 49t47 67 42 89 27 115 11 145q0 84 66 157t171 89q-5 10-5 21 0 23 16 38t38 16 38-16 16-38q0-11-5-21 106-16 171-89t66-157q0-78 11-145t28-115 41-89 48-67 50-49z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="plus-squared" unicode="" d="M714 321v72q0 14-10 25t-25 10h-179v179q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-179h-178q-15 0-25-10t-11-25v-72q0-14 11-25t25-10h178v-179q0-14 11-25t25-11h71q15 0 25 11t11 25v179h179q14 0 25 10t10 25z m143 304v-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="laptop" unicode="" d="M232 143q-37 0-63 26t-26 63v393q0 37 26 63t63 26h607q37 0 63-26t27-63v-393q0-37-27-63t-63-26h-607z m-18 482v-393q0-7 6-13t12-5h607q8 0 13 5t5 13v393q0 7-5 12t-13 6h-607q-7 0-12-6t-6-12z m768-518h89v-54q0-22-26-37t-63-16h-893q-36 0-63 16t-26 37v54h982z m-402-54q9 0 9 9t-9 9h-89q-9 0-9-9t9-9h89z" horiz-adv-x="1071.4" />
|
|
||||||
|
|
||||||
<glyph glyph-name="quote-right" unicode="" d="M429 678v-392q0-58-23-111t-61-91-91-61-111-23h-36q-14 0-25 11t-11 25v71q0 15 11 25t25 11h36q59 0 101 42t42 101v17q0 23-16 38t-38 16h-125q-44 0-76 31t-31 76v214q0 45 31 76t76 32h214q45 0 76-32t32-76z m500 0v-392q0-58-23-111t-61-91-91-61-111-23h-36q-14 0-25 11t-11 25v71q0 15 11 25t25 11h36q59 0 101 42t42 101v17q0 23-16 38t-38 16h-125q-44 0-76 31t-31 76v214q0 45 31 76t76 32h214q45 0 76-32t32-76z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="reply" unicode="" d="M1000 232q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="smile" unicode="" d="M633 257q-21-67-77-109t-127-41-128 41-77 109q-4 14 3 27t21 18q14 4 27-2t17-22q14-44 52-72t85-28 84 28 52 72q4 15 18 22t27 2 21-18 2-27z m-276 243q0-30-21-51t-50-21-51 21-21 51 21 50 51 21 50-21 21-50z m286 0q0-30-21-51t-51-21-50 21-21 51 21 50 50 21 51-21 21-50z m143-143q0 73-29 139t-76 114-114 76-138 28-139-28-114-76-76-114-29-139 29-139 76-113 114-77 139-28 138 28 114 77 76 113 29 139z m71 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
|
|
||||||
|
|
||||||
<glyph glyph-name="lock-open-alt" unicode="" d="M589 428q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-14-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" />
|
|
||||||
|
|
||||||
<glyph glyph-name="ellipsis" unicode="" d="M214 446v-107q0-22-15-38t-38-15h-107q-23 0-38 15t-16 38v107q0 23 16 38t38 16h107q22 0 38-16t15-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-15 38v107q0 23 15 38t38 16h107q23 0 38-16t16-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-16 38v107q0 23 16 38t38 16h107q23 0 38-16t16-38z" horiz-adv-x="785.7" />
|
|
||||||
|
|
||||||
<glyph glyph-name="play-circled" unicode="" d="M429 786q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m214-460q18 10 18 31t-18 31l-304 178q-17 11-35 1-18-11-18-31v-358q0-20 18-31 9-4 17-4 10 0 18 5z" horiz-adv-x="857.1" />
|
|
||||||
|
|
||||||
<glyph glyph-name="thumbs-up-alt" unicode="" d="M143 107q0 15-11 25t-25 11q-15 0-25-11t-11-25q0-15 11-25t25-11q15 0 25 11t11 25z m89 286v-357q0-15-10-25t-26-11h-160q-15 0-25 11t-11 25v357q0 14 11 25t25 10h160q15 0 26-10t10-25z m661 0q0-48-31-83 9-25 9-43 1-42-24-76 9-31 0-66-9-31-31-52 5-62-27-101-36-43-110-44h-72q-37 0-80 9t-68 16-67 22q-69 24-88 25-15 0-25 11t-11 25v357q0 14 10 25t24 11q13 1 42 33t57 67q38 49 56 67 10 10 17 27t10 27 8 34q4 22 7 34t11 29 19 28q10 11 25 11 25 0 46-6t33-15 22-22 14-25 7-28 2-25 1-22q0-21-6-43t-10-33-16-31q-1-4-5-10t-6-13-5-13h155q43 0 75-32t32-75z" horiz-adv-x="928.6" />
|
|
||||||
|
|
||||||
<glyph glyph-name="apple" unicode="" d="M777 179q-21-70-68-139-72-110-144-110-27 0-78 18-48 18-84 18-34 0-79-19-45-19-74-19-85 0-168 145-82 146-82 281 0 127 63 208 63 81 159 81 40 0 98-17 58-17 77-17 25 0 80 19 57 19 97 19 66 0 119-36 29-20 58-56-44-37-64-66-36-52-36-115 0-69 38-125t88-70z m-209 655q0-34-17-76-16-42-52-77-30-30-60-40-20-7-58-10 2 83 44 143 41 60 139 83 1-2 2-6t1-6q0-2 0-6t1-5z" horiz-adv-x="785.7" />
|
|
||||||
|
|
||||||
<glyph glyph-name="android" unicode="" d="M275 588q9 0 16 6t6 15-6 16-16 6-15-6-6-16 6-15 15-6z m236 0q9 0 15 6t6 15-6 16-15 6-16-6-6-16 6-15 16-6z m-453-103q23 0 40-17t16-40v-240q0-24-16-41t-40-17-41 17-17 41v240q0 23 17 40t41 17z m591-11v-371q0-26-18-44t-43-18h-42v-127q0-24-16-40t-41-17-41 17-17 40v127h-77v-127q0-24-16-40t-41-17q-24 0-40 17t-17 40l-1 127h-41q-26 0-43 18t-18 44v371h512z m-129 226q59-30 95-85t36-121h-516q0 66 35 121t96 85l-39 73q-4 8 2 12 8 3 12-4l40-74q53 24 112 24t112-24l40 74q4 7 11 4 7-4 3-12z m266-272v-240q0-24-17-41t-41-17q-23 0-40 17t-17 41v240q0 24 17 40t40 17q24 0 41-17t17-40z" horiz-adv-x="785.7" />
|
|
||||||
|
|
||||||
<glyph glyph-name="paper-plane-empty" unicode="" d="M984 851q19-13 15-36l-142-857q-3-16-18-25-8-5-18-5-6 0-13 3l-294 120-166-182q-10-12-27-12-7 0-12 2-11 4-17 13t-6 21v252l-264 108q-20 8-22 30-2 22 18 33l928 536q20 12 38-1z m-190-837l123 739-800-462 187-76 482 356-267-444z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="binoculars" unicode="" d="M393 678v-428q0-15-11-25t-25-11v-321q0-15-10-25t-26-11h-285q-15 0-25 11t-11 25v285l139 488q4 12 17 12h237z m178 0v-392h-142v392h142z m429-500v-285q0-15-11-25t-25-11h-285q-15 0-25 11t-11 25v321q-15 0-25 11t-11 25v428h237q13 0 17-12z m-589 661v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z m375 0v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z" horiz-adv-x="1000" />
|
|
||||||
|
|
||||||
<glyph glyph-name="user-plus" unicode="" d="M393 357q-89 0-152 63t-62 151 62 152 152 63 151-63 63-152-63-151-151-63z m536-71h196q7 0 13-6t5-12v-107q0-8-5-13t-13-5h-196v-197q0-7-6-12t-12-6h-107q-8 0-13 6t-5 12v197h-197q-7 0-12 5t-6 13v107q0 7 6 12t12 6h197v196q0 7 5 13t13 5h107q7 0 12-5t6-13v-196z m-411-125q0-29 21-51t50-21h143v-133q-38-28-95-28h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q11 0 22-10 44-34 86-51t92-17 92 17 86 51q11 10 22 10 73 0 121-54h-125q-29 0-50-21t-21-50v-107z" horiz-adv-x="1142.9" />
|
|
||||||
|
|
||||||
<glyph glyph-name="hashtag" unicode="" d="M553 286l36 142h-142l-36-142h142z m429 281l-32-125q-4-14-17-14h-182l-36-142h173q9 0 14-7 6-8 4-16l-32-125q-2-13-17-13h-182l-45-183q-4-14-18-14h-125q-9 0-14 7-5 7-4 16l44 174h-142l-45-183q-4-14-17-14h-126q-8 0-14 7-5 7-3 16l43 174h-173q-9 0-14 7-5 6-4 15l32 125q4 14 17 14h182l36 142h-173q-9 0-14 7-6 8-4 16l32 125q2 13 17 13h182l46 183q3 14 17 14h125q9 0 14-7 5-7 4-16l-44-174h142l45 183q4 14 18 14h125q8 0 14-7 5-7 3-16l-43-174h173q9 0 14-7 5-6 4-15z" horiz-adv-x="1000" />
|
|
||||||
</font>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 28 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/fontello.1575660578688.css
vendored
BIN
priv/static/fontello.1575660578688.css
vendored
Binary file not shown.
BIN
priv/static/fontello.1575662648966.css
vendored
BIN
priv/static/fontello.1575662648966.css
vendored
Binary file not shown.
|
@ -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"><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link rel=stylesheet href=/static/font/css/lato.css><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/lato.css><link href=/static/css/vendors~app.b2603a50868c68a1c192.css rel=stylesheet><link href=/static/css/app.1055039ce3f2fe4dd110.css rel=stylesheet><link href=/static/fontello.1583694403265.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.de343579e844e698d456.js></script><script type=text/javascript src=/static/js/app.a39dbef9004abe6273c9.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"><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link rel=stylesheet href=/static/font/css/lato.css><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/lato.css><link href=/static/css/vendors~app.b2603a50868c68a1c192.css rel=stylesheet><link href=/static/css/app.1055039ce3f2fe4dd110.css rel=stylesheet><link href=/static/fontello.1587222923489.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.de343579e844e698d456.js></script><script type=text/javascript src=/static/js/app.2156f8b23e368ba319e5.js></script></body></html>
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
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 a new issue