forked from AkkomaGang/akkoma
Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into feature/jobs
# Conflicts: # lib/pleroma/web/activity_pub/activity_pub.ex # lib/pleroma/web/federator/federator.ex
This commit is contained in:
commit
3a3a3996b7
604 changed files with 6727 additions and 418 deletions
|
@ -146,6 +146,7 @@
|
||||||
banner_upload_limit: 4_000_000,
|
banner_upload_limit: 4_000_000,
|
||||||
registrations_open: true,
|
registrations_open: true,
|
||||||
federating: true,
|
federating: true,
|
||||||
|
federation_reachability_timeout_days: 7,
|
||||||
allow_relay: true,
|
allow_relay: true,
|
||||||
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
|
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
|
||||||
public: true,
|
public: true,
|
||||||
|
@ -226,7 +227,9 @@
|
||||||
allow_followersonly: false,
|
allow_followersonly: false,
|
||||||
allow_direct: false
|
allow_direct: false
|
||||||
|
|
||||||
config :pleroma, :mrf_hellthread, threshold: 10
|
config :pleroma, :mrf_hellthread,
|
||||||
|
delist_threshold: 5,
|
||||||
|
reject_threshold: 10
|
||||||
|
|
||||||
config :pleroma, :mrf_simple,
|
config :pleroma, :mrf_simple,
|
||||||
media_removal: [],
|
media_removal: [],
|
||||||
|
@ -235,6 +238,8 @@
|
||||||
reject: [],
|
reject: [],
|
||||||
accept: []
|
accept: []
|
||||||
|
|
||||||
|
config :pleroma, :rich_media, enabled: true
|
||||||
|
|
||||||
config :pleroma, :media_proxy,
|
config :pleroma, :media_proxy,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
proxy_opts: [
|
proxy_opts: [
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
config :pleroma, :websub, Pleroma.Web.WebsubMock
|
config :pleroma, :websub, Pleroma.Web.WebsubMock
|
||||||
config :pleroma, :ostatus, Pleroma.Web.OStatusMock
|
config :pleroma, :ostatus, Pleroma.Web.OStatusMock
|
||||||
config :tesla, adapter: Tesla.Mock
|
config :tesla, adapter: Tesla.Mock
|
||||||
|
config :pleroma, :rich_media, enabled: false
|
||||||
|
|
||||||
config :web_push_encryption, :vapid_details,
|
config :web_push_encryption, :vapid_details,
|
||||||
subject: "mailto:administrator@example.com",
|
subject: "mailto:administrator@example.com",
|
||||||
|
|
|
@ -52,6 +52,7 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
|
||||||
* `confirm`
|
* `confirm`
|
||||||
* `captcha_solution`: optional, contains provider-specific captcha solution,
|
* `captcha_solution`: optional, contains provider-specific captcha solution,
|
||||||
* `captcha_token`: optional, contains provider-specific captcha token
|
* `captcha_token`: optional, contains provider-specific captcha token
|
||||||
|
* `token`: invite token required when the registerations aren't public.
|
||||||
* Response: JSON. Returns a user object on success, otherwise returns `{"error": "error_msg"}`
|
* Response: JSON. Returns a user object on success, otherwise returns `{"error": "error_msg"}`
|
||||||
* Example response:
|
* Example response:
|
||||||
```
|
```
|
||||||
|
|
|
@ -17,7 +17,7 @@ Note: `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`.
|
||||||
|
|
||||||
## Pleroma.Upload.Filter.Mogrify
|
## Pleroma.Upload.Filter.Mogrify
|
||||||
|
|
||||||
* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", {"impode", "1"}]`.
|
* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"impode", "1"}]`.
|
||||||
|
|
||||||
## Pleroma.Upload.Filter.Dedupe
|
## Pleroma.Upload.Filter.Dedupe
|
||||||
|
|
||||||
|
@ -73,6 +73,7 @@ config :pleroma, Pleroma.Mailer,
|
||||||
* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
|
* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
|
||||||
* `account_activation_required`: Require users to confirm their emails before signing in.
|
* `account_activation_required`: Require users to confirm their emails before signing in.
|
||||||
* `federating`: Enable federation with other instances
|
* `federating`: Enable federation with other instances
|
||||||
|
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
|
||||||
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance
|
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance
|
||||||
* `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
|
* `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
|
||||||
* `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesn’t modify activities (default)
|
* `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesn’t modify activities (default)
|
||||||
|
@ -124,7 +125,7 @@ This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:i
|
||||||
|
|
||||||
* `theme`: Which theme to use, they are defined in ``styles.json``
|
* `theme`: Which theme to use, they are defined in ``styles.json``
|
||||||
* `logo`: URL of the logo, defaults to Pleroma’s logo
|
* `logo`: URL of the logo, defaults to Pleroma’s logo
|
||||||
* `logo_mask`: Whenether to mask the logo
|
* `logo_mask`: Whether to use only the logo's shape as a mask (true) or as a regular image (false)
|
||||||
* `logo_margin`: What margin to use around the logo
|
* `logo_margin`: What margin to use around the logo
|
||||||
* `background`: URL of the background, unless viewing a user profile with a background that is set
|
* `background`: URL of the background, unless viewing a user profile with a background that is set
|
||||||
* `redirect_root_no_login`: relative URL which indicates where to redirect when a user isn’t logged in.
|
* `redirect_root_no_login`: relative URL which indicates where to redirect when a user isn’t logged in.
|
||||||
|
@ -148,7 +149,8 @@ This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:i
|
||||||
* `allow_direct`: whether to allow direct messages
|
* `allow_direct`: whether to allow direct messages
|
||||||
|
|
||||||
## :mrf_hellthread
|
## :mrf_hellthread
|
||||||
* `threshold`: Number of mentioned users after which the message gets discarded as spam
|
* `delist_threshold`: Number of mentioned users after which the message gets delisted (the message can still be seen, but it will not show up in public timelines and mentioned users won't get notifications about it). Set to 0 to disable.
|
||||||
|
* `reject_threshold`: Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.
|
||||||
|
|
||||||
## :media_proxy
|
## :media_proxy
|
||||||
* `enabled`: Enables proxying of remote media to the instance’s proxy
|
* `enabled`: Enables proxying of remote media to the instance’s proxy
|
||||||
|
@ -252,6 +254,9 @@ This config contains two queues: `federator_incoming` and `federator_outgoing`.
|
||||||
* Pleroma.Web.Metadata.Providers.TwitterCard
|
* Pleroma.Web.Metadata.Providers.TwitterCard
|
||||||
* `unfurl_nsfw`: If set to `true` nsfw attachments will be shown in previews
|
* `unfurl_nsfw`: If set to `true` nsfw attachments will be shown in previews
|
||||||
|
|
||||||
|
## :rich_media
|
||||||
|
* `enabled`: if enabled the instance will parse metadata from attached links to generate link previews
|
||||||
|
|
||||||
## :hackney_pools
|
## :hackney_pools
|
||||||
|
|
||||||
Advanced. Tweaks Hackney (http client) connections pools.
|
Advanced. Tweaks Hackney (http client) connections pools.
|
||||||
|
|
|
@ -6,11 +6,13 @@ defmodule Pleroma.Application do
|
||||||
use Application
|
use Application
|
||||||
import Supervisor.Spec
|
import Supervisor.Spec
|
||||||
|
|
||||||
@name "Pleroma"
|
@name Mix.Project.config()[:name]
|
||||||
@version Mix.Project.config()[:version]
|
@version Mix.Project.config()[:version]
|
||||||
|
@repository Mix.Project.config()[:source_url]
|
||||||
def name, do: @name
|
def name, do: @name
|
||||||
def version, do: @version
|
def version, do: @version
|
||||||
def named_version(), do: @name <> " " <> @version
|
def named_version(), do: @name <> " " <> @version
|
||||||
|
def repository, do: @repository
|
||||||
|
|
||||||
def user_agent() do
|
def user_agent() do
|
||||||
info = "#{Pleroma.Web.base_url()} <#{Pleroma.Config.get([:instance, :email], "")}>"
|
info = "#{Pleroma.Web.base_url()} <#{Pleroma.Config.get([:instance, :email], "")}>"
|
||||||
|
|
|
@ -12,6 +12,13 @@ def check_frontend_config_mechanism() do
|
||||||
You are using the old configuration mechanism for the frontend. Please check config.md.
|
You are using the old configuration mechanism for the frontend. Please check config.md.
|
||||||
""")
|
""")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if Pleroma.Config.get(:mrf_hellthread, :threshold) do
|
||||||
|
Logger.warn("""
|
||||||
|
!!!DEPRECATION WARNING!!!
|
||||||
|
You are using the old configuration mechanism for the hellthread filter. Please check config.md.
|
||||||
|
""")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def warn do
|
def warn do
|
||||||
|
|
36
lib/pleroma/instances.ex
Normal file
36
lib/pleroma/instances.ex
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
defmodule Pleroma.Instances do
|
||||||
|
@moduledoc "Instances context."
|
||||||
|
|
||||||
|
@adapter Pleroma.Instances.Instance
|
||||||
|
|
||||||
|
defdelegate filter_reachable(urls_or_hosts), to: @adapter
|
||||||
|
defdelegate reachable?(url_or_host), to: @adapter
|
||||||
|
defdelegate set_reachable(url_or_host), to: @adapter
|
||||||
|
defdelegate set_unreachable(url_or_host, unreachable_since \\ nil), to: @adapter
|
||||||
|
|
||||||
|
def set_consistently_unreachable(url_or_host),
|
||||||
|
do: set_unreachable(url_or_host, reachability_datetime_threshold())
|
||||||
|
|
||||||
|
def reachability_datetime_threshold do
|
||||||
|
federation_reachability_timeout_days =
|
||||||
|
Pleroma.Config.get(:instance)[:federation_reachability_timeout_days] || 0
|
||||||
|
|
||||||
|
if federation_reachability_timeout_days > 0 do
|
||||||
|
NaiveDateTime.add(
|
||||||
|
NaiveDateTime.utc_now(),
|
||||||
|
-federation_reachability_timeout_days * 24 * 3600,
|
||||||
|
:second
|
||||||
|
)
|
||||||
|
else
|
||||||
|
~N[0000-01-01 00:00:00]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def host(url_or_host) when is_binary(url_or_host) do
|
||||||
|
if url_or_host =~ ~r/^http/i do
|
||||||
|
URI.parse(url_or_host).host
|
||||||
|
else
|
||||||
|
url_or_host
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
113
lib/pleroma/instances/instance.ex
Normal file
113
lib/pleroma/instances/instance.ex
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
defmodule Pleroma.Instances.Instance do
|
||||||
|
@moduledoc "Instance."
|
||||||
|
|
||||||
|
alias Pleroma.Instances
|
||||||
|
alias Pleroma.Instances.Instance
|
||||||
|
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
import Ecto.{Query, Changeset}
|
||||||
|
|
||||||
|
alias Pleroma.Repo
|
||||||
|
|
||||||
|
schema "instances" do
|
||||||
|
field(:host, :string)
|
||||||
|
field(:unreachable_since, :naive_datetime)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
defdelegate host(url_or_host), to: Instances
|
||||||
|
|
||||||
|
def changeset(struct, params \\ %{}) do
|
||||||
|
struct
|
||||||
|
|> cast(params, [:host, :unreachable_since])
|
||||||
|
|> validate_required([:host])
|
||||||
|
|> unique_constraint(:host)
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_reachable([]), do: %{}
|
||||||
|
|
||||||
|
def filter_reachable(urls_or_hosts) when is_list(urls_or_hosts) do
|
||||||
|
hosts =
|
||||||
|
urls_or_hosts
|
||||||
|
|> Enum.map(&(&1 && host(&1)))
|
||||||
|
|> Enum.filter(&(to_string(&1) != ""))
|
||||||
|
|
||||||
|
unreachable_since_by_host =
|
||||||
|
Repo.all(
|
||||||
|
from(i in Instance,
|
||||||
|
where: i.host in ^hosts,
|
||||||
|
select: {i.host, i.unreachable_since}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> Map.new(& &1)
|
||||||
|
|
||||||
|
reachability_datetime_threshold = Instances.reachability_datetime_threshold()
|
||||||
|
|
||||||
|
for entry <- Enum.filter(urls_or_hosts, &is_binary/1) do
|
||||||
|
host = host(entry)
|
||||||
|
unreachable_since = unreachable_since_by_host[host]
|
||||||
|
|
||||||
|
if !unreachable_since ||
|
||||||
|
NaiveDateTime.compare(unreachable_since, reachability_datetime_threshold) == :gt do
|
||||||
|
{entry, unreachable_since}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
|> Map.new(& &1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def reachable?(url_or_host) when is_binary(url_or_host) do
|
||||||
|
!Repo.one(
|
||||||
|
from(i in Instance,
|
||||||
|
where:
|
||||||
|
i.host == ^host(url_or_host) and
|
||||||
|
i.unreachable_since <= ^Instances.reachability_datetime_threshold(),
|
||||||
|
select: true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def reachable?(_), do: true
|
||||||
|
|
||||||
|
def set_reachable(url_or_host) when is_binary(url_or_host) do
|
||||||
|
with host <- host(url_or_host),
|
||||||
|
%Instance{} = existing_record <- Repo.get_by(Instance, %{host: host}) do
|
||||||
|
{:ok, _instance} =
|
||||||
|
existing_record
|
||||||
|
|> changeset(%{unreachable_since: nil})
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_reachable(_), do: {:error, nil}
|
||||||
|
|
||||||
|
def set_unreachable(url_or_host, unreachable_since \\ nil)
|
||||||
|
|
||||||
|
def set_unreachable(url_or_host, unreachable_since) when is_binary(url_or_host) do
|
||||||
|
unreachable_since = unreachable_since || DateTime.utc_now()
|
||||||
|
host = host(url_or_host)
|
||||||
|
existing_record = Repo.get_by(Instance, %{host: host})
|
||||||
|
|
||||||
|
changes = %{unreachable_since: unreachable_since}
|
||||||
|
|
||||||
|
cond do
|
||||||
|
is_nil(existing_record) ->
|
||||||
|
%Instance{}
|
||||||
|
|> changeset(Map.put(changes, :host, host))
|
||||||
|
|> Repo.insert()
|
||||||
|
|
||||||
|
existing_record.unreachable_since &&
|
||||||
|
NaiveDateTime.compare(existing_record.unreachable_since, unreachable_since) != :gt ->
|
||||||
|
{:ok, existing_record}
|
||||||
|
|
||||||
|
true ->
|
||||||
|
existing_record
|
||||||
|
|> changeset(changes)
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_unreachable(_, _), do: {:error, nil}
|
||||||
|
end
|
|
@ -31,8 +31,8 @@ def get_by_ap_id(ap_id) do
|
||||||
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
|
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize(obj) when is_map(obj), do: Object.get_by_ap_id(obj["id"])
|
def normalize(%{"id" => ap_id}), do: normalize(ap_id)
|
||||||
def normalize(ap_id) when is_binary(ap_id), do: Object.get_by_ap_id(ap_id)
|
def normalize(ap_id) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id)
|
||||||
def normalize(_), do: nil
|
def normalize(_), do: nil
|
||||||
|
|
||||||
# Owned objects can only be mutated by their owner
|
# Owned objects can only be mutated by their owner
|
||||||
|
@ -42,24 +42,18 @@ def authorize_mutation(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}),
|
||||||
# Legacy objects can be mutated by anybody
|
# Legacy objects can be mutated by anybody
|
||||||
def authorize_mutation(%Object{}, %User{}), do: true
|
def authorize_mutation(%Object{}, %User{}), do: true
|
||||||
|
|
||||||
if Mix.env() == :test do
|
def get_cached_by_ap_id(ap_id) do
|
||||||
def get_cached_by_ap_id(ap_id) do
|
key = "object:#{ap_id}"
|
||||||
get_by_ap_id(ap_id)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
def get_cached_by_ap_id(ap_id) do
|
|
||||||
key = "object:#{ap_id}"
|
|
||||||
|
|
||||||
Cachex.fetch!(:object_cache, key, fn _ ->
|
Cachex.fetch!(:object_cache, key, fn _ ->
|
||||||
object = get_by_ap_id(ap_id)
|
object = get_by_ap_id(ap_id)
|
||||||
|
|
||||||
if object do
|
if object do
|
||||||
{:commit, object}
|
{:commit, object}
|
||||||
else
|
else
|
||||||
{:ignore, object}
|
{:ignore, object}
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def context_mapping(context) do
|
def context_mapping(context) do
|
||||||
|
@ -90,4 +84,17 @@ def delete(%Object{data: %{"id" => id}} = object) do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_cache(%Object{data: %{"id" => ap_id}} = object) do
|
||||||
|
Cachex.put(:object_cache, "object:#{ap_id}", object)
|
||||||
|
{:ok, object}
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_and_set_cache(changeset) do
|
||||||
|
with {:ok, object} <- Repo.update(changeset) do
|
||||||
|
set_cache(object)
|
||||||
|
else
|
||||||
|
e -> e
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ def file_path(path) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@only ~w(index.html static emoji packs sounds images instance favicon.png)
|
@only ~w(index.html static emoji packs sounds images instance favicon.png sw.js sw-pleroma.js)
|
||||||
|
|
||||||
def init(opts) do
|
def init(opts) do
|
||||||
opts
|
opts
|
||||||
|
|
|
@ -124,10 +124,10 @@ defp get_opts(opts) do
|
||||||
|
|
||||||
:pleroma, Pleroma.Upload, [filters: [Pleroma.Upload.Filter.Mogrify]]
|
:pleroma, Pleroma.Upload, [filters: [Pleroma.Upload.Filter.Mogrify]]
|
||||||
|
|
||||||
:pleroma, Pleroma.Upload.Filter.Mogrify, args: "strip"
|
:pleroma, Pleroma.Upload.Filter.Mogrify, args: ["strip", "auto-orient"]
|
||||||
""")
|
""")
|
||||||
|
|
||||||
Pleroma.Config.put([Pleroma.Upload.Filter.Mogrify], args: "strip")
|
Pleroma.Config.put([Pleroma.Upload.Filter.Mogrify], args: ["strip", "auto-orient"])
|
||||||
Map.put(opts, :filters, opts.filters ++ [Pleroma.Upload.Filter.Mogrify])
|
Map.put(opts, :filters, opts.filters ++ [Pleroma.Upload.Filter.Mogrify])
|
||||||
else
|
else
|
||||||
opts
|
opts
|
||||||
|
|
|
@ -39,6 +39,7 @@ defmodule Pleroma.User do
|
||||||
field(:follower_address, :string)
|
field(:follower_address, :string)
|
||||||
field(:search_rank, :float, virtual: true)
|
field(:search_rank, :float, virtual: true)
|
||||||
field(:tags, {:array, :string}, default: [])
|
field(:tags, {:array, :string}, default: [])
|
||||||
|
field(:bookmarks, {:array, :string}, default: [])
|
||||||
field(:last_refreshed_at, :naive_datetime)
|
field(:last_refreshed_at, :naive_datetime)
|
||||||
has_many(:notifications, Notification)
|
has_many(:notifications, Notification)
|
||||||
embeds_one(:info, Pleroma.User.Info)
|
embeds_one(:info, Pleroma.User.Info)
|
||||||
|
@ -314,7 +315,16 @@ def follow_all(follower, followeds) do
|
||||||
q =
|
q =
|
||||||
from(u in User,
|
from(u in User,
|
||||||
where: u.id == ^follower.id,
|
where: u.id == ^follower.id,
|
||||||
update: [set: [following: fragment("array_cat(?, ?)", u.following, ^followed_addresses)]]
|
update: [
|
||||||
|
set: [
|
||||||
|
following:
|
||||||
|
fragment(
|
||||||
|
"array(select distinct unnest (array_cat(?, ?)))",
|
||||||
|
u.following,
|
||||||
|
^followed_addresses
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
{1, [follower]} = Repo.update_all(q, [], returning: true)
|
{1, [follower]} = Repo.update_all(q, [], returning: true)
|
||||||
|
@ -1161,6 +1171,22 @@ defp update_tags(%User{} = user, new_tags) do
|
||||||
updated_user
|
updated_user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def bookmark(%User{} = user, status_id) do
|
||||||
|
bookmarks = Enum.uniq(user.bookmarks ++ [status_id])
|
||||||
|
update_bookmarks(user, bookmarks)
|
||||||
|
end
|
||||||
|
|
||||||
|
def unbookmark(%User{} = user, status_id) do
|
||||||
|
bookmarks = Enum.uniq(user.bookmarks -- [status_id])
|
||||||
|
update_bookmarks(user, bookmarks)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_bookmarks(%User{} = user, bookmarks) do
|
||||||
|
user
|
||||||
|
|> change(%{bookmarks: bookmarks})
|
||||||
|
|> update_and_set_cache
|
||||||
|
end
|
||||||
|
|
||||||
defp normalize_tags(tags) do
|
defp normalize_tags(tags) do
|
||||||
[tags]
|
[tags]
|
||||||
|> List.flatten()
|
|> List.flatten()
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
alias Pleroma.{Activity, Repo, Object, Upload, User, Notification}
|
alias Pleroma.{Activity, Repo, Object, Upload, User, Notification, Instances}
|
||||||
alias Pleroma.Web.ActivityPub.{Transmogrifier, MRF}
|
alias Pleroma.Web.ActivityPub.{Transmogrifier, MRF}
|
||||||
alias Pleroma.Web.WebFinger
|
alias Pleroma.Web.WebFinger
|
||||||
alias Pleroma.Web.Federator
|
alias Pleroma.Web.Federator
|
||||||
|
@ -734,7 +734,7 @@ def should_federate?(inbox, public) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def publish(actor, activity) do
|
def publish(actor, activity) do
|
||||||
followers =
|
remote_followers =
|
||||||
if actor.follower_address in activity.recipients do
|
if actor.follower_address in activity.recipients do
|
||||||
{:ok, followers} = User.get_followers(actor)
|
{:ok, followers} = User.get_followers(actor)
|
||||||
followers |> Enum.filter(&(!&1.local))
|
followers |> Enum.filter(&(!&1.local))
|
||||||
|
@ -747,24 +747,26 @@ def publish(actor, activity) do
|
||||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
json = Jason.encode!(data)
|
json = Jason.encode!(data)
|
||||||
|
|
||||||
(Pleroma.Web.Salmon.remote_users(activity) ++ followers)
|
(Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
|
||||||
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|
||||||
|> Enum.map(fn %{info: %{source_data: data}} ->
|
|> Enum.map(fn %{info: %{source_data: data}} ->
|
||||||
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
|
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
|
||||||
end)
|
end)
|
||||||
|> Enum.uniq()
|
|> Enum.uniq()
|
||||||
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|
||||||
|> Enum.each(fn inbox ->
|
|> Instances.filter_reachable()
|
||||||
|
|> Enum.each(fn {inbox, unreachable_since} ->
|
||||||
Federator.publish_single_ap(%{
|
Federator.publish_single_ap(%{
|
||||||
inbox: inbox,
|
inbox: inbox,
|
||||||
json: json,
|
json: json,
|
||||||
actor: actor,
|
actor: actor,
|
||||||
id: activity.data["id"]
|
id: activity.data["id"],
|
||||||
|
unreachable_since: unreachable_since
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
|
def publish_one(%{inbox: inbox, json: json, actor: actor, id: id} = params) do
|
||||||
Logger.info("Federating #{id} to #{inbox}")
|
Logger.info("Federating #{id} to #{inbox}")
|
||||||
host = URI.parse(inbox).host
|
host = URI.parse(inbox).host
|
||||||
|
|
||||||
|
@ -777,15 +779,26 @@ def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
|
||||||
digest: digest
|
digest: digest
|
||||||
})
|
})
|
||||||
|
|
||||||
@httpoison.post(
|
with {:ok, %{status: code}} when code in 200..299 <-
|
||||||
inbox,
|
result =
|
||||||
json,
|
@httpoison.post(
|
||||||
[
|
inbox,
|
||||||
{"Content-Type", "application/activity+json"},
|
json,
|
||||||
{"signature", signature},
|
[
|
||||||
{"digest", digest}
|
{"Content-Type", "application/activity+json"},
|
||||||
]
|
{"signature", signature},
|
||||||
)
|
{"digest", digest}
|
||||||
|
]
|
||||||
|
) do
|
||||||
|
if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
|
||||||
|
do: Instances.set_reachable(inbox)
|
||||||
|
|
||||||
|
result
|
||||||
|
else
|
||||||
|
{_post_result, response} ->
|
||||||
|
unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
|
||||||
|
{:error, response}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
alias Pleroma.{Activity, User, Object}
|
alias Pleroma.{Activity, User, Object}
|
||||||
alias Pleroma.Web.ActivityPub.{ObjectView, UserView}
|
alias Pleroma.Web.ActivityPub.{ObjectView, UserView}
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
@ -17,6 +18,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
action_fallback(:errors)
|
action_fallback(:errors)
|
||||||
|
|
||||||
plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
|
plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
|
||||||
|
plug(:set_requester_reachable when action in [:inbox])
|
||||||
plug(:relay_active? when action in [:relay])
|
plug(:relay_active? when action in [:relay])
|
||||||
|
|
||||||
def relay_active?(conn, _) do
|
def relay_active?(conn, _) do
|
||||||
|
@ -289,4 +291,13 @@ def errors(conn, _e) do
|
||||||
|> put_status(500)
|
|> put_status(500)
|
||||||
|> json("error")
|
|> json("error")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp set_requester_reachable(%Plug.Conn{} = conn, _) do
|
||||||
|
with actor <- conn.params["actor"],
|
||||||
|
true <- is_binary(actor) do
|
||||||
|
Pleroma.Instances.set_reachable(actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,20 +3,46 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
|
defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
|
||||||
|
alias Pleroma.User
|
||||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
@impl true
|
defp delist_message(message) do
|
||||||
def filter(%{"type" => "Create"} = object) do
|
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address
|
||||||
threshold = Pleroma.Config.get([:mrf_hellthread, :threshold])
|
|
||||||
recipients = (object["to"] || []) ++ (object["cc"] || [])
|
|
||||||
|
|
||||||
if length(recipients) > threshold do
|
message
|
||||||
{:reject, nil}
|
|> Map.put("to", [follower_collection])
|
||||||
else
|
|> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"])
|
||||||
{:ok, object}
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(%{"type" => "Create"} = message) do
|
||||||
|
delist_threshold = Pleroma.Config.get([:mrf_hellthread, :delist_threshold])
|
||||||
|
|
||||||
|
reject_threshold =
|
||||||
|
Pleroma.Config.get(
|
||||||
|
[:mrf_hellthread, :reject_threshold],
|
||||||
|
Pleroma.Config.get([:mrf_hellthread, :threshold])
|
||||||
|
)
|
||||||
|
|
||||||
|
recipients = (message["to"] || []) ++ (message["cc"] || [])
|
||||||
|
|
||||||
|
cond do
|
||||||
|
length(recipients) > reject_threshold and reject_threshold > 0 ->
|
||||||
|
{:reject, nil}
|
||||||
|
|
||||||
|
length(recipients) > delist_threshold and delist_threshold > 0 ->
|
||||||
|
if Enum.member?(message["to"], "https://www.w3.org/ns/activitystreams#Public") or
|
||||||
|
Enum.member?(message["cc"], "https://www.w3.org/ns/activitystreams#Public") do
|
||||||
|
{:ok, delist_message(message)}
|
||||||
|
else
|
||||||
|
{:ok, message}
|
||||||
|
end
|
||||||
|
|
||||||
|
true ->
|
||||||
|
{:ok, message}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(object), do: {:ok, object}
|
def filter(message), do: {:ok, message}
|
||||||
end
|
end
|
||||||
|
|
|
@ -285,7 +285,7 @@ def update_element_in_object(property, element, object) do
|
||||||
|> Map.put("#{property}_count", length(element))
|
|> Map.put("#{property}_count", length(element))
|
||||||
|> Map.put("#{property}s", element),
|
|> Map.put("#{property}s", element),
|
||||||
changeset <- Changeset.change(object, data: new_data),
|
changeset <- Changeset.change(object, data: new_data),
|
||||||
{:ok, object} <- Repo.update(changeset),
|
{:ok, object} <- Object.update_and_set_cache(changeset),
|
||||||
_ <- update_object_in_activities(object) do
|
_ <- update_object_in_activities(object) do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,7 +25,7 @@ defmodule Pleroma.Web.Endpoint do
|
||||||
at: "/",
|
at: "/",
|
||||||
from: :pleroma,
|
from: :pleroma,
|
||||||
only:
|
only:
|
||||||
~w(index.html static finmoji emoji packs sounds images instance sw.js favicon.png schemas doc)
|
~w(index.html static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Code reloading can be explicitly enabled under the
|
# Code reloading can be explicitly enabled under the
|
||||||
|
@ -82,4 +82,8 @@ def load_from_system_env(config) do
|
||||||
port = System.get_env("PORT") || raise "expected the PORT environment variable to be set"
|
port = System.get_env("PORT") || raise "expected the PORT environment variable to be set"
|
||||||
{:ok, Keyword.put(config, :http, [:inet6, port: port])}
|
{:ok, Keyword.put(config, :http, [:inet6, port: port])}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def websocket_url do
|
||||||
|
String.replace_leading(url(), "http", "ws")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.Federator do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Jobs
|
alias Pleroma.Jobs
|
||||||
alias Pleroma.Web.{WebFinger, Websub}
|
alias Pleroma.Web.{WebFinger, Websub, Salmon}
|
||||||
alias Pleroma.Web.Federator.RetryQueue
|
alias Pleroma.Web.Federator.RetryQueue
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Relay
|
alias Pleroma.Web.ActivityPub.Relay
|
||||||
|
@ -58,6 +58,10 @@ def refresh_subscriptions() do
|
||||||
Jobs.enqueue(:federator_out, __MODULE__, [:refresh_subscriptions])
|
Jobs.enqueue(:federator_out, __MODULE__, [:refresh_subscriptions])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def publish_single_salmon(params) do
|
||||||
|
Jobs.enqueue(:federator_out, __MODULE__, [:publish_single_salmon, params])
|
||||||
|
end
|
||||||
|
|
||||||
# Job Worker Callbacks
|
# Job Worker Callbacks
|
||||||
|
|
||||||
def perform(:refresh_subscriptions) do
|
def perform(:refresh_subscriptions) do
|
||||||
|
@ -145,6 +149,10 @@ def perform(:incoming_ap_doc, params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def perform(:publish_single_salmon, params) do
|
||||||
|
Salmon.send_to_user(params)
|
||||||
|
end
|
||||||
|
|
||||||
def perform(:publish_single_ap, params) do
|
def perform(:publish_single_ap, params) do
|
||||||
case ActivityPub.publish_one(params) do
|
case ActivityPub.publish_one(params) do
|
||||||
{:ok, _} ->
|
{:ok, _} ->
|
||||||
|
|
|
@ -138,7 +138,7 @@ def masto_instance(conn, _params) do
|
||||||
version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
|
version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
|
||||||
email: Keyword.get(instance, :email),
|
email: Keyword.get(instance, :email),
|
||||||
urls: %{
|
urls: %{
|
||||||
streaming_api: String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws")
|
streaming_api: Pleroma.Web.Endpoint.websocket_url()
|
||||||
},
|
},
|
||||||
stats: Stats.get_stats(),
|
stats: Stats.get_stats(),
|
||||||
thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg",
|
thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg",
|
||||||
|
@ -423,6 +423,28 @@ def unpin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||||
|
%User{} = user <- User.get_by_nickname(user.nickname),
|
||||||
|
true <- ActivityPub.visible_for_user?(activity, user),
|
||||||
|
{:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do
|
||||||
|
conn
|
||||||
|
|> put_view(StatusView)
|
||||||
|
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||||
|
%User{} = user <- User.get_by_nickname(user.nickname),
|
||||||
|
true <- ActivityPub.visible_for_user?(activity, user),
|
||||||
|
{:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do
|
||||||
|
conn
|
||||||
|
|> put_view(StatusView)
|
||||||
|
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def notifications(%{assigns: %{user: user}} = conn, params) do
|
def notifications(%{assigns: %{user: user}} = conn, params) do
|
||||||
notifications = Notification.for_user(user, params)
|
notifications = Notification.for_user(user, params)
|
||||||
|
|
||||||
|
@ -859,6 +881,19 @@ def favourites(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def bookmarks(%{assigns: %{user: user}} = conn, _) do
|
||||||
|
user = Repo.get(User, user.id)
|
||||||
|
|
||||||
|
activities =
|
||||||
|
user.bookmarks
|
||||||
|
|> Enum.map(fn id -> Activity.get_create_by_object_ap_id(id) end)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_view(StatusView)
|
||||||
|
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
||||||
|
end
|
||||||
|
|
||||||
def get_lists(%{assigns: %{user: user}} = conn, opts) do
|
def get_lists(%{assigns: %{user: user}} = conn, opts) do
|
||||||
lists = Pleroma.List.for_user(user, opts)
|
lists = Pleroma.List.for_user(user, opts)
|
||||||
res = ListView.render("lists.json", lists: lists)
|
res = ListView.render("lists.json", lists: lists)
|
||||||
|
@ -870,7 +905,10 @@ def get_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
res = ListView.render("list.json", list: list)
|
res = ListView.render("list.json", list: list)
|
||||||
json(conn, res)
|
json(conn, res)
|
||||||
else
|
else
|
||||||
_e -> json(conn, "error")
|
_e ->
|
||||||
|
conn
|
||||||
|
|> put_status(404)
|
||||||
|
|> json(%{error: "Record not found"})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,7 @@ def render(
|
||||||
favourites_count: 0,
|
favourites_count: 0,
|
||||||
reblogged: false,
|
reblogged: false,
|
||||||
favourited: false,
|
favourited: false,
|
||||||
|
bookmarked: false,
|
||||||
muted: false,
|
muted: false,
|
||||||
pinned: pinned?(activity, user),
|
pinned: pinned?(activity, user),
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
|
@ -121,6 +122,7 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
||||||
|
|
||||||
repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || [])
|
repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || [])
|
||||||
favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
|
favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
|
||||||
|
bookmarked = opts[:for] && object["id"] in opts[:for].bookmarks
|
||||||
|
|
||||||
attachment_data = object["attachment"] || []
|
attachment_data = object["attachment"] || []
|
||||||
attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
|
attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
|
||||||
|
@ -157,6 +159,7 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
||||||
favourites_count: like_count,
|
favourites_count: like_count,
|
||||||
reblogged: present?(repeated),
|
reblogged: present?(repeated),
|
||||||
favourited: present?(favorited),
|
favourited: present?(favorited),
|
||||||
|
bookmarked: present?(bookmarked),
|
||||||
muted: false,
|
muted: false,
|
||||||
pinned: pinned?(activity, user),
|
pinned: pinned?(activity, user),
|
||||||
sensitive: sensitive,
|
sensitive: sensitive,
|
||||||
|
|
|
@ -19,6 +19,10 @@ def schemas(conn, _params) do
|
||||||
%{
|
%{
|
||||||
rel: "http://nodeinfo.diaspora.software/ns/schema/2.0",
|
rel: "http://nodeinfo.diaspora.software/ns/schema/2.0",
|
||||||
href: Web.base_url() <> "/nodeinfo/2.0.json"
|
href: Web.base_url() <> "/nodeinfo/2.0.json"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
rel: "http://nodeinfo.diaspora.software/ns/schema/2.1",
|
||||||
|
href: Web.base_url() <> "/nodeinfo/2.1.json"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -26,8 +30,9 @@ def schemas(conn, _params) do
|
||||||
json(conn, response)
|
json(conn, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json
|
# returns a nodeinfo 2.0 map, since 2.1 just adds a repository field
|
||||||
def nodeinfo(conn, %{"version" => "2.0"}) do
|
# under software.
|
||||||
|
def raw_nodeinfo() do
|
||||||
instance = Application.get_env(:pleroma, :instance)
|
instance = Application.get_env(:pleroma, :instance)
|
||||||
media_proxy = Application.get_env(:pleroma, :media_proxy)
|
media_proxy = Application.get_env(:pleroma, :media_proxy)
|
||||||
suggestions = Application.get_env(:pleroma, :suggestions)
|
suggestions = Application.get_env(:pleroma, :suggestions)
|
||||||
|
@ -98,10 +103,10 @@ def nodeinfo(conn, %{"version" => "2.0"}) do
|
||||||
]
|
]
|
||||||
|> Enum.filter(& &1)
|
|> Enum.filter(& &1)
|
||||||
|
|
||||||
response = %{
|
%{
|
||||||
version: "2.0",
|
version: "2.0",
|
||||||
software: %{
|
software: %{
|
||||||
name: Pleroma.Application.name(),
|
name: Pleroma.Application.name() |> String.downcase(),
|
||||||
version: Pleroma.Application.version()
|
version: Pleroma.Application.version()
|
||||||
},
|
},
|
||||||
protocols: ["ostatus", "activitypub"],
|
protocols: ["ostatus", "activitypub"],
|
||||||
|
@ -142,12 +147,37 @@ def nodeinfo(conn, %{"version" => "2.0"}) do
|
||||||
restrictedNicknames: Pleroma.Config.get([Pleroma.User, :restricted_nicknames])
|
restrictedNicknames: Pleroma.Config.get([Pleroma.User, :restricted_nicknames])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json
|
||||||
|
# and https://github.com/jhass/nodeinfo/blob/master/schemas/2.1/schema.json
|
||||||
|
def nodeinfo(conn, %{"version" => "2.0"}) do
|
||||||
conn
|
conn
|
||||||
|> put_resp_header(
|
|> put_resp_header(
|
||||||
"content-type",
|
"content-type",
|
||||||
"application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8"
|
"application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8"
|
||||||
)
|
)
|
||||||
|
|> json(raw_nodeinfo())
|
||||||
|
end
|
||||||
|
|
||||||
|
def nodeinfo(conn, %{"version" => "2.1"}) do
|
||||||
|
raw_response = raw_nodeinfo()
|
||||||
|
|
||||||
|
updated_software =
|
||||||
|
raw_response
|
||||||
|
|> Map.get(:software)
|
||||||
|
|> Map.put(:repository, Pleroma.Application.repository())
|
||||||
|
|
||||||
|
response =
|
||||||
|
raw_response
|
||||||
|
|> Map.put(:software, updated_software)
|
||||||
|
|> Map.put(:version, "2.1")
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_header(
|
||||||
|
"content-type",
|
||||||
|
"application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.1#; charset=utf-8"
|
||||||
|
)
|
||||||
|> json(response)
|
|> json(response)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,9 @@ def remote_follow_path do
|
||||||
|
|
||||||
def handle_incoming(xml_string) do
|
def handle_incoming(xml_string) do
|
||||||
with doc when doc != :error <- parse_document(xml_string) do
|
with doc when doc != :error <- parse_document(xml_string) do
|
||||||
|
with {:ok, actor_user} <- find_make_or_update_user(doc),
|
||||||
|
do: Pleroma.Instances.set_reachable(actor_user.ap_id)
|
||||||
|
|
||||||
entries = :xmerl_xpath.string('//entry', doc)
|
entries = :xmerl_xpath.string('//entry', doc)
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
|
|
|
@ -14,6 +14,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
|
||||||
plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
|
plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
|
||||||
|
|
||||||
action_fallback(:errors)
|
action_fallback(:errors)
|
||||||
|
|
||||||
def feed_redirect(conn, %{"nickname" => nickname}) do
|
def feed_redirect(conn, %{"nickname" => nickname}) do
|
||||||
|
|
|
@ -7,7 +7,8 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
||||||
alias Pleroma.Web.RichMedia.Parser
|
alias Pleroma.Web.RichMedia.Parser
|
||||||
|
|
||||||
def fetch_data_for_activity(%Activity{} = activity) do
|
def fetch_data_for_activity(%Activity{} = activity) do
|
||||||
with %Object{} = object <- Object.normalize(activity.data["object"]),
|
with true <- Pleroma.Config.get([:rich_media, :enabled]),
|
||||||
|
%Object{} = object <- Object.normalize(activity.data["object"]),
|
||||||
{:ok, page_url} <- HTML.extract_first_external_url(object, object.data["content"]),
|
{:ok, page_url} <- HTML.extract_first_external_url(object, object.data["content"]),
|
||||||
{:ok, rich_media} <- Parser.parse(page_url) do
|
{:ok, rich_media} <- Parser.parse(page_url) do
|
||||||
%{page_url: page_url, rich_media: rich_media}
|
%{page_url: page_url, rich_media: rich_media}
|
||||||
|
|
|
@ -30,7 +30,7 @@ defp parse_url(url) do
|
||||||
try do
|
try do
|
||||||
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: [pool: :media])
|
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: [pool: :media])
|
||||||
|
|
||||||
html |> maybe_parse() |> get_parsed_data()
|
html |> maybe_parse() |> clean_parsed_data() |> check_parsed_data()
|
||||||
rescue
|
rescue
|
||||||
e ->
|
e ->
|
||||||
{:error, "Parsing error: #{inspect(e)}"}
|
{:error, "Parsing error: #{inspect(e)}"}
|
||||||
|
@ -46,11 +46,33 @@ defp maybe_parse(html) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_parsed_data(%{title: title} = data) when is_binary(title) and byte_size(title) > 0 do
|
defp check_parsed_data(%{title: title} = data) when is_binary(title) and byte_size(title) > 0 do
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_parsed_data(data) do
|
defp check_parsed_data(data) do
|
||||||
{:error, "Found metadata was invalid or incomplete: #{inspect(data)}"}
|
{:error, "Found metadata was invalid or incomplete: #{inspect(data)}"}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp string_is_valid_unicode(data) when is_binary(data) do
|
||||||
|
data
|
||||||
|
|> :unicode.characters_to_binary()
|
||||||
|
|> clean_string()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp string_is_valid_unicode(data), do: {:ok, data}
|
||||||
|
|
||||||
|
defp clean_string({:error, _, _}), do: {:error, "Invalid data"}
|
||||||
|
defp clean_string(data), do: {:ok, data}
|
||||||
|
|
||||||
|
defp clean_parsed_data(data) do
|
||||||
|
data
|
||||||
|
|> Enum.reject(fn {_, val} ->
|
||||||
|
case string_is_valid_unicode(val) do
|
||||||
|
{:ok, _} -> false
|
||||||
|
_ -> true
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Map.new()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -185,6 +185,7 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/timelines/direct", MastodonAPIController, :dm_timeline)
|
get("/timelines/direct", MastodonAPIController, :dm_timeline)
|
||||||
|
|
||||||
get("/favourites", MastodonAPIController, :favourites)
|
get("/favourites", MastodonAPIController, :favourites)
|
||||||
|
get("/bookmarks", MastodonAPIController, :bookmarks)
|
||||||
|
|
||||||
post("/statuses", MastodonAPIController, :post_status)
|
post("/statuses", MastodonAPIController, :post_status)
|
||||||
delete("/statuses/:id", MastodonAPIController, :delete_status)
|
delete("/statuses/:id", MastodonAPIController, :delete_status)
|
||||||
|
@ -195,6 +196,8 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/statuses/:id/unfavourite", MastodonAPIController, :unfav_status)
|
post("/statuses/:id/unfavourite", MastodonAPIController, :unfav_status)
|
||||||
post("/statuses/:id/pin", MastodonAPIController, :pin_status)
|
post("/statuses/:id/pin", MastodonAPIController, :pin_status)
|
||||||
post("/statuses/:id/unpin", MastodonAPIController, :unpin_status)
|
post("/statuses/:id/unpin", MastodonAPIController, :unpin_status)
|
||||||
|
post("/statuses/:id/bookmark", MastodonAPIController, :bookmark_status)
|
||||||
|
post("/statuses/:id/unbookmark", MastodonAPIController, :unbookmark_status)
|
||||||
|
|
||||||
post("/notifications/clear", MastodonAPIController, :clear_notifications)
|
post("/notifications/clear", MastodonAPIController, :clear_notifications)
|
||||||
post("/notifications/dismiss", MastodonAPIController, :dismiss_notification)
|
post("/notifications/dismiss", MastodonAPIController, :dismiss_notification)
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.Salmon do
|
||||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||||
|
|
||||||
use Bitwise
|
use Bitwise
|
||||||
|
alias Pleroma.Instances
|
||||||
alias Pleroma.Web.XML
|
alias Pleroma.Web.XML
|
||||||
alias Pleroma.Web.OStatus.ActivityRepresenter
|
alias Pleroma.Web.OStatus.ActivityRepresenter
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
@ -161,25 +162,31 @@ def remote_users(%{data: %{"to" => to} = data}) do
|
||||||
|> Enum.filter(fn user -> user && !user.local end)
|
|> Enum.filter(fn user -> user && !user.local end)
|
||||||
end
|
end
|
||||||
|
|
||||||
# push an activity to remote accounts
|
@doc "Pushes an activity to remote account."
|
||||||
#
|
def send_to_user(%{recipient: %{info: %{salmon: salmon}}} = params),
|
||||||
defp send_to_user(%{info: %{salmon: salmon}}, feed, poster),
|
do: send_to_user(Map.put(params, :recipient, salmon))
|
||||||
do: send_to_user(salmon, feed, poster)
|
|
||||||
|
|
||||||
defp send_to_user(url, feed, poster) when is_binary(url) do
|
def send_to_user(%{recipient: url, feed: feed, poster: poster} = params) when is_binary(url) do
|
||||||
with {:ok, %{status: code}} <-
|
with {:ok, %{status: code}} when code in 200..299 <-
|
||||||
poster.(
|
poster.(
|
||||||
url,
|
url,
|
||||||
feed,
|
feed,
|
||||||
[{"Content-Type", "application/magic-envelope+xml"}]
|
[{"Content-Type", "application/magic-envelope+xml"}]
|
||||||
) do
|
) do
|
||||||
|
if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
|
||||||
|
do: Instances.set_reachable(url)
|
||||||
|
|
||||||
Logger.debug(fn -> "Pushed to #{url}, code #{code}" end)
|
Logger.debug(fn -> "Pushed to #{url}, code #{code}" end)
|
||||||
|
:ok
|
||||||
else
|
else
|
||||||
e -> Logger.debug(fn -> "Pushing Salmon to #{url} failed, #{inspect(e)}" end)
|
e ->
|
||||||
|
unless params[:unreachable_since], do: Instances.set_reachable(url)
|
||||||
|
Logger.debug(fn -> "Pushing Salmon to #{url} failed, #{inspect(e)}" end)
|
||||||
|
:error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp send_to_user(_, _, _), do: nil
|
def send_to_user(_), do: :noop
|
||||||
|
|
||||||
@supported_activities [
|
@supported_activities [
|
||||||
"Create",
|
"Create",
|
||||||
|
@ -209,12 +216,23 @@ def publish(%{info: %{keys: keys}} = user, %{data: %{"type" => type}} = activity
|
||||||
{:ok, private, _} = keys_from_pem(keys)
|
{:ok, private, _} = keys_from_pem(keys)
|
||||||
{:ok, feed} = encode(private, feed)
|
{:ok, feed} = encode(private, feed)
|
||||||
|
|
||||||
remote_users(activity)
|
remote_users = remote_users(activity)
|
||||||
|
|
||||||
|
salmon_urls = Enum.map(remote_users, & &1.info.salmon)
|
||||||
|
reachable_urls_metadata = Instances.filter_reachable(salmon_urls)
|
||||||
|
reachable_urls = Map.keys(reachable_urls_metadata)
|
||||||
|
|
||||||
|
remote_users
|
||||||
|
|> Enum.filter(&(&1.info.salmon in reachable_urls))
|
||||||
|> Enum.each(fn remote_user ->
|
|> Enum.each(fn remote_user ->
|
||||||
Task.start(fn ->
|
Logger.debug(fn -> "Sending Salmon to #{remote_user.ap_id}" end)
|
||||||
Logger.debug(fn -> "Sending Salmon to #{remote_user.ap_id}" end)
|
|
||||||
send_to_user(remote_user, feed, poster)
|
Pleroma.Web.Federator.publish_single_salmon(%{
|
||||||
end)
|
recipient: remote_user,
|
||||||
|
feed: feed,
|
||||||
|
poster: poster,
|
||||||
|
unreachable_since: reachable_urls_metadata[remote_user.info.salmon]
|
||||||
|
})
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset=utf-8 />
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui" />
|
||||||
<title>
|
<title>
|
||||||
<%= Application.get_env(:pleroma, :instance)[:name] %>
|
<%= Application.get_env(:pleroma, :instance)[:name] %>
|
||||||
</title>
|
</title>
|
||||||
|
|
|
@ -1,23 +1,28 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang='en'>
|
<html lang='en'>
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset='utf-8'>
|
||||||
|
<meta content='width=device-width, initial-scale=1' name='viewport'>
|
||||||
<title>
|
<title>
|
||||||
<%= Application.get_env(:pleroma, :instance)[:name] %>
|
<%= Application.get_env(:pleroma, :instance)[:name] %>
|
||||||
</title>
|
</title>
|
||||||
<meta charset='utf-8'>
|
|
||||||
<meta content='width=device-width, initial-scale=1' name='viewport'>
|
|
||||||
<link rel="icon" type="image/png" href="/favicon.png"/>
|
<link rel="icon" type="image/png" href="/favicon.png"/>
|
||||||
<link rel="stylesheet" media="all" href="/packs/common.css" />
|
<script crossorigin='anonymous' src="/packs/locales.js"></script>
|
||||||
<link rel="stylesheet" media="all" href="/packs/default.css" />
|
<script crossorigin='anonymous' src="/packs/locales/glitch/en.js"></script>
|
||||||
|
|
||||||
<script src="/packs/common.js"></script>
|
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/getting_started.js'>
|
||||||
<script src="/packs/locale_en.js"></script>
|
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/compose.js'>
|
||||||
<link as='script' crossorigin='anonymous' href='/packs/features/getting_started.js' rel='preload'>
|
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/home_timeline.js'>
|
||||||
<link as='script' crossorigin='anonymous' href='/packs/features/compose.js' rel='preload'>
|
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/notifications.js'>
|
||||||
<link as='script' crossorigin='anonymous' href='/packs/features/home_timeline.js' rel='preload'>
|
|
||||||
<link as='script' crossorigin='anonymous' href='/packs/features/notifications.js' rel='preload'>
|
|
||||||
<script id='initial-state' type='application/json'><%= raw @initial_state %></script>
|
<script id='initial-state' type='application/json'><%= raw @initial_state %></script>
|
||||||
<script src="/packs/application.js"></script>
|
|
||||||
|
<script src="/packs/core/common.js"></script>
|
||||||
|
<link rel="stylesheet" media="all" href="/packs/core/common.css" />
|
||||||
|
|
||||||
|
<script src="/packs/flavours/glitch/common.js"></script>
|
||||||
|
<link rel="stylesheet" media="all" href="/packs/flavours/glitch/common.css" />
|
||||||
|
|
||||||
|
<script src="/packs/flavours/glitch/home.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class='app-body no-reduce-motion system-font'>
|
<body class='app-body no-reduce-motion system-font'>
|
||||||
<div class='app-holder' data-props='{"locale":"en"}' id='mastodon'>
|
<div class='app-holder' data-props='{"locale":"en"}' id='mastodon'>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
defmodule Pleroma.Web.Websub do
|
defmodule Pleroma.Web.Websub do
|
||||||
alias Ecto.Changeset
|
alias Ecto.Changeset
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.Instances
|
||||||
alias Pleroma.Web.Websub.{WebsubServerSubscription, WebsubClientSubscription}
|
alias Pleroma.Web.Websub.{WebsubServerSubscription, WebsubClientSubscription}
|
||||||
alias Pleroma.Web.OStatus.FeedRepresenter
|
alias Pleroma.Web.OStatus.FeedRepresenter
|
||||||
alias Pleroma.Web.{XML, Endpoint, OStatus, Federator}
|
alias Pleroma.Web.{XML, Endpoint, OStatus, Federator}
|
||||||
|
@ -53,28 +54,34 @@ def verify(subscription, getter \\ &@httpoison.get/3) do
|
||||||
]
|
]
|
||||||
def publish(topic, user, %{data: %{"type" => type}} = activity)
|
def publish(topic, user, %{data: %{"type" => type}} = activity)
|
||||||
when type in @supported_activities do
|
when type in @supported_activities do
|
||||||
# TODO: Only send to still valid subscriptions.
|
response =
|
||||||
|
user
|
||||||
|
|> FeedRepresenter.to_simple_form([activity], [user])
|
||||||
|
|> :xmerl.export_simple(:xmerl_xml)
|
||||||
|
|> to_string
|
||||||
|
|
||||||
query =
|
query =
|
||||||
from(
|
from(
|
||||||
sub in WebsubServerSubscription,
|
sub in WebsubServerSubscription,
|
||||||
where: sub.topic == ^topic and sub.state == "active",
|
where: sub.topic == ^topic and sub.state == "active",
|
||||||
where: fragment("? > NOW()", sub.valid_until)
|
where: fragment("? > (NOW() at time zone 'UTC')", sub.valid_until)
|
||||||
)
|
)
|
||||||
|
|
||||||
subscriptions = Repo.all(query)
|
subscriptions = Repo.all(query)
|
||||||
|
|
||||||
Enum.each(subscriptions, fn sub ->
|
callbacks = Enum.map(subscriptions, & &1.callback)
|
||||||
response =
|
reachable_callbacks_metadata = Instances.filter_reachable(callbacks)
|
||||||
user
|
reachable_callbacks = Map.keys(reachable_callbacks_metadata)
|
||||||
|> FeedRepresenter.to_simple_form([activity], [user])
|
|
||||||
|> :xmerl.export_simple(:xmerl_xml)
|
|
||||||
|> to_string
|
|
||||||
|
|
||||||
|
subscriptions
|
||||||
|
|> Enum.filter(&(&1.callback in reachable_callbacks))
|
||||||
|
|> Enum.each(fn sub ->
|
||||||
data = %{
|
data = %{
|
||||||
xml: response,
|
xml: response,
|
||||||
topic: topic,
|
topic: topic,
|
||||||
callback: sub.callback,
|
callback: sub.callback,
|
||||||
secret: sub.secret
|
secret: sub.secret,
|
||||||
|
unreachable_since: reachable_callbacks_metadata[sub.callback]
|
||||||
}
|
}
|
||||||
|
|
||||||
Federator.publish_single_websub(data)
|
Federator.publish_single_websub(data)
|
||||||
|
@ -263,11 +270,11 @@ def refresh_subscriptions(delta \\ 60 * 60 * 24) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def publish_one(%{xml: xml, topic: topic, callback: callback, secret: secret}) do
|
def publish_one(%{xml: xml, topic: topic, callback: callback, secret: secret} = params) do
|
||||||
signature = sign(secret || "", xml)
|
signature = sign(secret || "", xml)
|
||||||
Logger.info(fn -> "Pushing #{topic} to #{callback}" end)
|
Logger.info(fn -> "Pushing #{topic} to #{callback}" end)
|
||||||
|
|
||||||
with {:ok, %{status: code}} <-
|
with {:ok, %{status: code}} when code in 200..299 <-
|
||||||
@httpoison.post(
|
@httpoison.post(
|
||||||
callback,
|
callback,
|
||||||
xml,
|
xml,
|
||||||
|
@ -276,12 +283,16 @@ def publish_one(%{xml: xml, topic: topic, callback: callback, secret: secret}) d
|
||||||
{"X-Hub-Signature", "sha1=#{signature}"}
|
{"X-Hub-Signature", "sha1=#{signature}"}
|
||||||
]
|
]
|
||||||
) do
|
) do
|
||||||
|
if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
|
||||||
|
do: Instances.set_reachable(callback)
|
||||||
|
|
||||||
Logger.info(fn -> "Pushed to #{callback}, code #{code}" end)
|
Logger.info(fn -> "Pushed to #{callback}, code #{code}" end)
|
||||||
{:ok, code}
|
{:ok, code}
|
||||||
else
|
else
|
||||||
e ->
|
{_post_result, response} ->
|
||||||
Logger.debug(fn -> "Couldn't push to #{callback}, #{inspect(e)}" end)
|
unless params[:unreachable_since], do: Instances.set_reachable(callback)
|
||||||
{:error, e}
|
Logger.debug(fn -> "Couldn't push to #{callback}, #{inspect(response)}" end)
|
||||||
|
{:error, response}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,9 +4,11 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.Websub.WebsubController do
|
defmodule Pleroma.Web.Websub.WebsubController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
alias Pleroma.{Repo, User}
|
alias Pleroma.{Repo, User}
|
||||||
alias Pleroma.Web.{Websub, Federator}
|
alias Pleroma.Web.{Websub, Federator}
|
||||||
alias Pleroma.Web.Websub.WebsubClientSubscription
|
alias Pleroma.Web.Websub.WebsubClientSubscription
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddBookmarksToUsers do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:users) do
|
||||||
|
add :bookmarks, {:array, :string}, null: false, default: []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
15
priv/repo/migrations/20190123125546_create_instances.exs
Normal file
15
priv/repo/migrations/20190123125546_create_instances.exs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateInstances do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:instances) do
|
||||||
|
add :host, :string
|
||||||
|
add :unreachable_since, :naive_datetime
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create unique_index(:instances, [:host])
|
||||||
|
create index(:instances, [:unreachable_since])
|
||||||
|
end
|
||||||
|
end
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 378 B |
Binary file not shown.
After Width: | Height: | Size: 8.7 KiB |
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
BIN
priv/static/packs/core/admin.js
Normal file
BIN
priv/static/packs/core/admin.js
Normal file
Binary file not shown.
BIN
priv/static/packs/core/admin.js.map
Normal file
BIN
priv/static/packs/core/admin.js.map
Normal file
Binary file not shown.
BIN
priv/static/packs/core/common.css
Normal file
BIN
priv/static/packs/core/common.css
Normal file
Binary file not shown.
1
priv/static/packs/core/common.css.map
Normal file
1
priv/static/packs/core/common.css.map
Normal file
File diff suppressed because one or more lines are too long
BIN
priv/static/packs/core/common.js
Normal file
BIN
priv/static/packs/core/common.js
Normal file
Binary file not shown.
BIN
priv/static/packs/core/common.js.map
Normal file
BIN
priv/static/packs/core/common.js.map
Normal file
Binary file not shown.
BIN
priv/static/packs/core/embed.js
Normal file
BIN
priv/static/packs/core/embed.js
Normal file
Binary file not shown.
BIN
priv/static/packs/core/embed.js.map
Normal file
BIN
priv/static/packs/core/embed.js.map
Normal file
Binary file not shown.
BIN
priv/static/packs/core/mailer.css
Normal file
BIN
priv/static/packs/core/mailer.css
Normal file
Binary file not shown.
1
priv/static/packs/core/mailer.css.map
Normal file
1
priv/static/packs/core/mailer.css.map
Normal file
File diff suppressed because one or more lines are too long
BIN
priv/static/packs/core/mailer.js
Normal file
BIN
priv/static/packs/core/mailer.js
Normal file
Binary file not shown.
BIN
priv/static/packs/core/mailer.js.map
Normal file
BIN
priv/static/packs/core/mailer.js.map
Normal file
Binary file not shown.
BIN
priv/static/packs/core/public.js
Normal file
BIN
priv/static/packs/core/public.js
Normal file
Binary file not shown.
BIN
priv/static/packs/core/public.js.map
Normal file
BIN
priv/static/packs/core/public.js.map
Normal file
Binary file not shown.
BIN
priv/static/packs/core/settings.js
Normal file
BIN
priv/static/packs/core/settings.js
Normal file
Binary file not shown.
BIN
priv/static/packs/core/settings.js.map
Normal file
BIN
priv/static/packs/core/settings.js.map
Normal file
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue