forked from AkkomaGang/akkoma
Merge branch 'develop' into 'patch-2'
# Conflicts: # mix.exs
This commit is contained in:
commit
f2fa09c50f
278 changed files with 972 additions and 175 deletions
|
@ -13,6 +13,21 @@ Instead, overload the settings by editing the following files:
|
||||||
* `dev.secret.exs`: custom additional configuration for `MIX_ENV=dev`
|
* `dev.secret.exs`: custom additional configuration for `MIX_ENV=dev`
|
||||||
* `prod.secret.exs`: custom additional configuration for `MIX_ENV=prod`
|
* `prod.secret.exs`: custom additional configuration for `MIX_ENV=prod`
|
||||||
|
|
||||||
|
## Uploads configuration
|
||||||
|
|
||||||
|
To configure where to upload files, and wether or not
|
||||||
|
you want to remove automatically EXIF data from pictures
|
||||||
|
being uploaded.
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Upload,
|
||||||
|
uploads: "uploads",
|
||||||
|
strip_exif: false
|
||||||
|
|
||||||
|
* `uploads`: where to put the uploaded files, relative to pleroma's main directory.
|
||||||
|
* `strip_exif`: whether or not to remove EXIF data from uploaded pics automatically.
|
||||||
|
This needs Imagemagick installed on the system ( apt install imagemagick ).
|
||||||
|
|
||||||
|
|
||||||
## Block functionality
|
## Block functionality
|
||||||
|
|
||||||
config :pleroma, :activitypub,
|
config :pleroma, :activitypub,
|
||||||
|
|
|
@ -10,7 +10,11 @@
|
||||||
|
|
||||||
config :pleroma, Pleroma.Repo, types: Pleroma.PostgresTypes
|
config :pleroma, Pleroma.Repo, types: Pleroma.PostgresTypes
|
||||||
|
|
||||||
config :pleroma, Pleroma.Upload, uploads: "uploads"
|
config :pleroma, Pleroma.Upload,
|
||||||
|
uploads: "uploads",
|
||||||
|
strip_exif: false
|
||||||
|
|
||||||
|
config :pleroma, :emoji, shortcode_globs: ["/emoji/custom/**/*.png"]
|
||||||
|
|
||||||
# Configures the endpoint
|
# Configures the endpoint
|
||||||
config :pleroma, Pleroma.Web.Endpoint,
|
config :pleroma, Pleroma.Web.Endpoint,
|
||||||
|
@ -50,6 +54,7 @@
|
||||||
version: version,
|
version: version,
|
||||||
name: "Pleroma",
|
name: "Pleroma",
|
||||||
email: "example@example.com",
|
email: "example@example.com",
|
||||||
|
description: "A Pleroma instance, an alternative fediverse server",
|
||||||
limit: 5000,
|
limit: 5000,
|
||||||
upload_limit: 16_000_000,
|
upload_limit: 16_000_000,
|
||||||
registrations_open: true,
|
registrations_open: true,
|
||||||
|
@ -58,6 +63,19 @@
|
||||||
public: true,
|
public: true,
|
||||||
quarantined_instances: []
|
quarantined_instances: []
|
||||||
|
|
||||||
|
config :pleroma, :fe,
|
||||||
|
theme: "pleroma-dark",
|
||||||
|
logo: "/static/logo.png",
|
||||||
|
background: "/static/aurora_borealis.jpg",
|
||||||
|
redirect_root_no_login: "/main/all",
|
||||||
|
redirect_root_login: "/main/friends",
|
||||||
|
show_instance_panel: true,
|
||||||
|
show_who_to_follow_panel: false,
|
||||||
|
who_to_follow_provider:
|
||||||
|
"https://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-osa-api.cgi?{{host}}+{{user}}",
|
||||||
|
who_to_follow_link: "https://vinayaka.distsn.org/?{{host}}+{{user}}",
|
||||||
|
scope_options_enabled: false
|
||||||
|
|
||||||
config :pleroma, :activitypub,
|
config :pleroma, :activitypub,
|
||||||
accept_blocks: true,
|
accept_blocks: true,
|
||||||
unfollow_blocked: true,
|
unfollow_blocked: true,
|
||||||
|
@ -93,6 +111,13 @@
|
||||||
ip: {0, 0, 0, 0},
|
ip: {0, 0, 0, 0},
|
||||||
port: 9999
|
port: 9999
|
||||||
|
|
||||||
|
config :pleroma, :suggestions,
|
||||||
|
enabled: false,
|
||||||
|
third_party_engine:
|
||||||
|
"http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}",
|
||||||
|
timeout: 300_000,
|
||||||
|
web: "https://vinayaka.distsn.org/?{{host}}+{{user}}"
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
import_config "#{Mix.env()}.exs"
|
import_config "#{Mix.env()}.exs"
|
||||||
|
|
25
lib/mix/tasks/generate_invite_token.ex
Normal file
25
lib/mix/tasks/generate_invite_token.ex
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
defmodule Mix.Tasks.GenerateInviteToken do
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
@shortdoc "Generate invite token for user"
|
||||||
|
def run([]) do
|
||||||
|
Mix.Task.run("app.start")
|
||||||
|
|
||||||
|
with {:ok, token} <- Pleroma.UserInviteToken.create_token() do
|
||||||
|
IO.puts("Generated user invite token")
|
||||||
|
|
||||||
|
IO.puts(
|
||||||
|
"Url: #{
|
||||||
|
Pleroma.Web.Router.Helpers.redirect_url(
|
||||||
|
Pleroma.Web.Endpoint,
|
||||||
|
:registration_page,
|
||||||
|
token.token
|
||||||
|
)
|
||||||
|
}"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
IO.puts("Error creating token")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -78,4 +78,8 @@ def get_create_activity_by_object_ap_id(ap_id) when is_binary(ap_id) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_create_activity_by_object_ap_id(_), do: nil
|
def get_create_activity_by_object_ap_id(_), do: nil
|
||||||
|
|
||||||
|
def normalize(obj) when is_map(obj), do: Activity.get_by_ap_id(obj["id"])
|
||||||
|
def normalize(ap_id) when is_binary(ap_id), do: Activity.get_by_ap_id(ap_id)
|
||||||
|
def normalize(_), do: nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -116,7 +116,28 @@ def parse_mentions(text) do
|
||||||
_ -> []
|
_ -> []
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@emoji @finmoji_with_filenames ++ @emoji_from_file
|
@emoji_from_globs (
|
||||||
|
static_path = Path.join(:code.priv_dir(:pleroma), "static")
|
||||||
|
|
||||||
|
globs =
|
||||||
|
Application.get_env(:pleroma, :emoji, [])
|
||||||
|
|> Keyword.get(:shortcode_globs, [])
|
||||||
|
|
||||||
|
paths =
|
||||||
|
Enum.map(globs, fn glob ->
|
||||||
|
Path.join(static_path, glob)
|
||||||
|
|> Path.wildcard()
|
||||||
|
end)
|
||||||
|
|> Enum.concat()
|
||||||
|
|
||||||
|
Enum.map(paths, fn path ->
|
||||||
|
shortcode = Path.basename(path, Path.extname(path))
|
||||||
|
external_path = Path.join("/", Path.relative_to(path, static_path))
|
||||||
|
{shortcode, external_path}
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
|
||||||
|
@emoji @finmoji_with_filenames ++ @emoji_from_globs ++ @emoji_from_file
|
||||||
|
|
||||||
def emojify(text, emoji \\ @emoji)
|
def emojify(text, emoji \\ @emoji)
|
||||||
def emojify(text, nil), do: text
|
def emojify(text, nil), do: text
|
||||||
|
@ -223,8 +244,8 @@ def add_hashtag_links({subs, text}, tags) do
|
||||||
|
|
||||||
subs =
|
subs =
|
||||||
subs ++
|
subs ++
|
||||||
Enum.map(tags, fn {_, tag, uuid} ->
|
Enum.map(tags, fn {tag_text, tag, uuid} ->
|
||||||
url = "<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>##{tag}</a>"
|
url = "<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>#{tag_text}</a>"
|
||||||
{uuid, url}
|
{uuid, url}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ def info(text) do
|
||||||
|
|
||||||
String.split(text, "\r")
|
String.split(text, "\r")
|
||||||
|> Enum.map(fn text ->
|
|> Enum.map(fn text ->
|
||||||
"i#{text}\tfake\(NULL)\t0\r\n"
|
"i#{text}\tfake\t(NULL)\t0\r\n"
|
||||||
end)
|
end)
|
||||||
|> Enum.join("")
|
|> Enum.join("")
|
||||||
end
|
end
|
||||||
|
@ -77,14 +77,14 @@ def render_activities(activities) do
|
||||||
|
|
||||||
link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <>
|
link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <>
|
||||||
info("#{like_count} likes, #{announcement_count} repeats") <>
|
info("#{like_count} likes, #{announcement_count} repeats") <>
|
||||||
"\r\n" <>
|
"i\tfake\t(NULL)\t0\r\n" <>
|
||||||
info(
|
info(
|
||||||
HtmlSanitizeEx.strip_tags(
|
HtmlSanitizeEx.strip_tags(
|
||||||
String.replace(activity.data["object"]["content"], "<br>", "\r")
|
String.replace(activity.data["object"]["content"], "<br>", "\r")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|> Enum.join("\r\n")
|
|> Enum.join("i\tfake\t(NULL)\t0\r\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
def response("") do
|
def response("") do
|
||||||
|
|
|
@ -27,6 +27,10 @@ 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(ap_id) when is_binary(ap_id), do: Object.get_by_ap_id(ap_id)
|
||||||
|
def normalize(_), do: nil
|
||||||
|
|
||||||
def get_cached_by_ap_id(ap_id) do
|
def get_cached_by_ap_id(ap_id) do
|
||||||
if Mix.env() == :test do
|
if Mix.env() == :test do
|
||||||
get_by_ap_id(ap_id)
|
get_by_ap_id(ap_id)
|
||||||
|
|
10
lib/pleroma/plugs/digest.ex
Normal file
10
lib/pleroma/plugs/digest.ex
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
defmodule Pleroma.Web.Plugs.DigestPlug do
|
||||||
|
alias Plug.Conn
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
def read_body(conn, opts) do
|
||||||
|
{:ok, body, conn} = Conn.read_body(conn, opts)
|
||||||
|
digest = "SHA-256=" <> (:crypto.hash(:sha256, body) |> Base.encode64())
|
||||||
|
{:ok, body, Conn.assign(conn, :digest, digest)}
|
||||||
|
end
|
||||||
|
end
|
|
@ -19,6 +19,8 @@ def call(conn, _opts) do
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
signature && String.contains?(signature, user) ->
|
signature && String.contains?(signature, user) ->
|
||||||
|
# set (request-target) header to the appropriate value
|
||||||
|
# we also replace the digest header with the one we computed
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> put_req_header(
|
|> put_req_header(
|
||||||
|
@ -26,6 +28,14 @@ def call(conn, _opts) do
|
||||||
String.downcase("#{conn.method}") <> " #{conn.request_path}"
|
String.downcase("#{conn.method}") <> " #{conn.request_path}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
if conn.assigns[:digest] do
|
||||||
|
conn
|
||||||
|
|> put_req_header("digest", conn.assigns[:digest])
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
end
|
||||||
|
|
||||||
assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
|
assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
|
||||||
|
|
||||||
signature ->
|
signature ->
|
||||||
|
|
|
@ -18,8 +18,10 @@ def store(%Plug.Upload{} = file, should_dedupe) do
|
||||||
File.cp!(file.path, result_file)
|
File.cp!(file.path, result_file)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
strip_exif_data(content_type, result_file)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"type" => "Image",
|
"type" => "Document",
|
||||||
"url" => [
|
"url" => [
|
||||||
%{
|
%{
|
||||||
"type" => "Link",
|
"type" => "Link",
|
||||||
|
@ -67,6 +69,8 @@ def store(%{"img" => "data:image/" <> image_data}, should_dedupe) do
|
||||||
File.rename(uuidpath, result_file)
|
File.rename(uuidpath, result_file)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
strip_exif_data(content_type, result_file)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"type" => "Image",
|
"type" => "Image",
|
||||||
"url" => [
|
"url" => [
|
||||||
|
@ -80,6 +84,16 @@ def store(%{"img" => "data:image/" <> image_data}, should_dedupe) do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def strip_exif_data(content_type, file) do
|
||||||
|
settings = Application.get_env(:pleroma, Pleroma.Upload)
|
||||||
|
do_strip = Keyword.fetch!(settings, :strip_exif)
|
||||||
|
[filetype, ext] = String.split(content_type, "/")
|
||||||
|
|
||||||
|
if filetype == "image" and do_strip == true do
|
||||||
|
Mogrify.open(file) |> Mogrify.custom("strip") |> Mogrify.save(in_place: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def upload_path do
|
def upload_path do
|
||||||
settings = Application.get_env(:pleroma, Pleroma.Upload)
|
settings = Application.get_env(:pleroma, Pleroma.Upload)
|
||||||
Keyword.fetch!(settings, :uploads)
|
Keyword.fetch!(settings, :uploads)
|
||||||
|
@ -110,20 +124,20 @@ defp get_name(file, uuid, type, should_dedupe) do
|
||||||
if should_dedupe do
|
if should_dedupe do
|
||||||
create_name(uuid, List.last(String.split(file.filename, ".")), type)
|
create_name(uuid, List.last(String.split(file.filename, ".")), type)
|
||||||
else
|
else
|
||||||
unless String.contains?(file.filename, ".") do
|
parts = String.split(file.filename, ".")
|
||||||
case type do
|
|
||||||
"image/png" -> file.filename <> ".png"
|
new_filename =
|
||||||
"image/jpeg" -> file.filename <> ".jpg"
|
if length(parts) > 1 do
|
||||||
"image/gif" -> file.filename <> ".gif"
|
Enum.drop(parts, -1) |> Enum.join(".")
|
||||||
"video/webm" -> file.filename <> ".webm"
|
|
||||||
"video/mp4" -> file.filename <> ".mp4"
|
|
||||||
"audio/mpeg" -> file.filename <> ".mp3"
|
|
||||||
"audio/ogg" -> file.filename <> ".ogg"
|
|
||||||
"audio/wav" -> file.filename <> ".wav"
|
|
||||||
_ -> file.filename
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
file.filename
|
Enum.join(parts)
|
||||||
|
end
|
||||||
|
|
||||||
|
case type do
|
||||||
|
"application/octet-stream" -> file.filename
|
||||||
|
"audio/mpeg" -> new_filename <> ".mp3"
|
||||||
|
"image/jpeg" -> new_filename <> ".jpg"
|
||||||
|
_ -> Enum.join([new_filename, String.split(type, "/") |> List.last()], ".")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -398,6 +398,7 @@ def get_follow_requests(%User{} = user) do
|
||||||
Enum.map(reqs, fn req -> req.actor end)
|
Enum.map(reqs, fn req -> req.actor end)
|
||||||
|> Enum.uniq()
|
|> Enum.uniq()
|
||||||
|> Enum.map(fn ap_id -> get_by_ap_id(ap_id) end)
|
|> Enum.map(fn ap_id -> get_by_ap_id(ap_id) end)
|
||||||
|
|> Enum.filter(fn u -> !following?(u, user) end)
|
||||||
|
|
||||||
{:ok, users}
|
{:ok, users}
|
||||||
end
|
end
|
||||||
|
@ -607,7 +608,7 @@ def delete(%User{} = user) do
|
||||||
|> Enum.each(fn activity ->
|
|> Enum.each(fn activity ->
|
||||||
case activity.data["type"] do
|
case activity.data["type"] do
|
||||||
"Create" ->
|
"Create" ->
|
||||||
ActivityPub.delete(Object.get_by_ap_id(activity.data["object"]["id"]))
|
ActivityPub.delete(Object.normalize(activity.data["object"]))
|
||||||
|
|
||||||
# TODO: Do something with likes, follows, repeats.
|
# TODO: Do something with likes, follows, repeats.
|
||||||
_ ->
|
_ ->
|
||||||
|
|
40
lib/pleroma/user_invite_token.ex
Normal file
40
lib/pleroma/user_invite_token.ex
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
defmodule Pleroma.UserInviteToken do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
alias Pleroma.{User, UserInviteToken, Repo}
|
||||||
|
|
||||||
|
schema "user_invite_tokens" do
|
||||||
|
field(:token, :string)
|
||||||
|
field(:used, :boolean, default: false)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_token do
|
||||||
|
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64()
|
||||||
|
|
||||||
|
token = %UserInviteToken{
|
||||||
|
used: false,
|
||||||
|
token: token
|
||||||
|
}
|
||||||
|
|
||||||
|
Repo.insert(token)
|
||||||
|
end
|
||||||
|
|
||||||
|
def used_changeset(struct) do
|
||||||
|
struct
|
||||||
|
|> cast(%{}, [])
|
||||||
|
|> put_change(:used, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_as_used(token) do
|
||||||
|
with %{used: false} = token <- Repo.get_by(UserInviteToken, %{token: token}),
|
||||||
|
{:ok, token} <- Repo.update(used_changeset(token)) do
|
||||||
|
{:ok, token}
|
||||||
|
else
|
||||||
|
_e -> {:error, token}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -30,7 +30,7 @@ defp check_actor_is_active(actor) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def insert(map, local \\ true) when is_map(map) do
|
def insert(map, local \\ true) when is_map(map) do
|
||||||
with nil <- Activity.get_by_ap_id(map["id"]),
|
with nil <- Activity.normalize(map),
|
||||||
map <- lazy_put_activity_defaults(map),
|
map <- lazy_put_activity_defaults(map),
|
||||||
:ok <- check_actor_is_active(map["actor"]),
|
:ok <- check_actor_is_active(map["actor"]),
|
||||||
{:ok, map} <- MRF.filter(map),
|
{:ok, map} <- MRF.filter(map),
|
||||||
|
@ -641,13 +641,23 @@ def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
|
||||||
Logger.info("Federating #{id} to #{inbox}")
|
Logger.info("Federating #{id} to #{inbox}")
|
||||||
host = URI.parse(inbox).host
|
host = URI.parse(inbox).host
|
||||||
|
|
||||||
|
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
|
||||||
|
|
||||||
signature =
|
signature =
|
||||||
Pleroma.Web.HTTPSignatures.sign(actor, %{host: host, "content-length": byte_size(json)})
|
Pleroma.Web.HTTPSignatures.sign(actor, %{
|
||||||
|
host: host,
|
||||||
|
"content-length": byte_size(json),
|
||||||
|
digest: digest
|
||||||
|
})
|
||||||
|
|
||||||
@httpoison.post(
|
@httpoison.post(
|
||||||
inbox,
|
inbox,
|
||||||
json,
|
json,
|
||||||
[{"Content-Type", "application/activity+json"}, {"signature", signature}],
|
[
|
||||||
|
{"Content-Type", "application/activity+json"},
|
||||||
|
{"signature", signature},
|
||||||
|
{"digest", digest}
|
||||||
|
],
|
||||||
hackney: [pool: :default]
|
hackney: [pool: :default]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -670,7 +680,7 @@ def fetch_object_from_id(id) do
|
||||||
recv_timeout: 20000
|
recv_timeout: 20000
|
||||||
),
|
),
|
||||||
{:ok, data} <- Jason.decode(body),
|
{:ok, data} <- Jason.decode(body),
|
||||||
nil <- Object.get_by_ap_id(data["id"]),
|
nil <- Object.normalize(data),
|
||||||
params <- %{
|
params <- %{
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
"to" => data["to"],
|
"to" => data["to"],
|
||||||
|
@ -679,7 +689,7 @@ def fetch_object_from_id(id) do
|
||||||
"object" => data
|
"object" => data
|
||||||
},
|
},
|
||||||
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
|
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
|
||||||
{:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
|
{:ok, Object.normalize(activity.data["object"])}
|
||||||
else
|
else
|
||||||
object = %Object{} ->
|
object = %Object{} ->
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
|
@ -688,7 +698,7 @@ def fetch_object_from_id(id) do
|
||||||
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
|
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
|
||||||
|
|
||||||
case OStatus.fetch_activity_from_url(id) do
|
case OStatus.fetch_activity_from_url(id) do
|
||||||
{:ok, [activity | _]} -> {:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
|
{:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"])}
|
||||||
e -> e
|
e -> e
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,18 +13,58 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
def get_actor(%{"actor" => actor}) when is_binary(actor) do
|
||||||
|
actor
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_actor(%{"actor" => actor}) when is_list(actor) do
|
||||||
|
Enum.at(actor, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_actor(%{"actor" => actor}) when is_map(actor) do
|
||||||
|
actor["id"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_actor(%{"actor" => actor_list}) do
|
||||||
|
Enum.find(actor_list, fn %{"type" => type} -> type == "Person" end)
|
||||||
|
|> Map.get("id")
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Modifies an incoming AP object (mastodon format) to our internal format.
|
Modifies an incoming AP object (mastodon format) to our internal format.
|
||||||
"""
|
"""
|
||||||
def fix_object(object) do
|
def fix_object(object) do
|
||||||
object
|
object
|
||||||
|> Map.put("actor", object["attributedTo"])
|
|> fix_actor
|
||||||
|> fix_attachments
|
|> fix_attachments
|
||||||
|> fix_context
|
|> fix_context
|
||||||
|> fix_in_reply_to
|
|> fix_in_reply_to
|
||||||
|> fix_emoji
|
|> fix_emoji
|
||||||
|> fix_tag
|
|> fix_tag
|
||||||
|> fix_content_map
|
|> fix_content_map
|
||||||
|
|> fix_addressing
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_addressing_list(map, field) do
|
||||||
|
if is_binary(map[field]) do
|
||||||
|
map
|
||||||
|
|> Map.put(field, [map[field]])
|
||||||
|
else
|
||||||
|
map
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_addressing(map) do
|
||||||
|
map
|
||||||
|
|> fix_addressing_list("to")
|
||||||
|
|> fix_addressing_list("cc")
|
||||||
|
|> fix_addressing_list("bto")
|
||||||
|
|> fix_addressing_list("bcc")
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_actor(%{"attributedTo" => actor} = object) do
|
||||||
|
object
|
||||||
|
|> Map.put("actor", get_actor(%{"actor" => actor}))
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object)
|
def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object)
|
||||||
|
@ -122,7 +162,14 @@ def fix_content_map(object), do: object
|
||||||
# TODO: validate those with a Ecto scheme
|
# TODO: validate those with a Ecto scheme
|
||||||
# - tags
|
# - tags
|
||||||
# - emoji
|
# - emoji
|
||||||
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
|
def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)
|
||||||
|
when objtype in ["Article", "Note"] do
|
||||||
|
actor = get_actor(data)
|
||||||
|
|
||||||
|
data =
|
||||||
|
Map.put(data, "actor", actor)
|
||||||
|
|> fix_addressing
|
||||||
|
|
||||||
with nil <- Activity.get_create_activity_by_object_ap_id(object["id"]),
|
with nil <- Activity.get_create_activity_by_object_ap_id(object["id"]),
|
||||||
%User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do
|
%User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do
|
||||||
object = fix_object(data["object"])
|
object = fix_object(data["object"])
|
||||||
|
@ -412,7 +459,7 @@ def handle_incoming(
|
||||||
def handle_incoming(_), do: :error
|
def handle_incoming(_), do: :error
|
||||||
|
|
||||||
def get_obj_helper(id) do
|
def get_obj_helper(id) do
|
||||||
if object = Object.get_by_ap_id(id), do: {:ok, object}, else: nil
|
if object = Object.normalize(id), do: {:ok, object}, else: nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) do
|
def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) do
|
||||||
|
@ -460,14 +507,7 @@ def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = obj
|
||||||
# Mastodon Accept/Reject requires a non-normalized object containing the actor URIs,
|
# Mastodon Accept/Reject requires a non-normalized object containing the actor URIs,
|
||||||
# because of course it does.
|
# because of course it does.
|
||||||
def prepare_outgoing(%{"type" => "Accept"} = data) do
|
def prepare_outgoing(%{"type" => "Accept"} = data) do
|
||||||
follow_activity_id =
|
with follow_activity <- Activity.normalize(data["object"]) do
|
||||||
if is_binary(data["object"]) do
|
|
||||||
data["object"]
|
|
||||||
else
|
|
||||||
data["object"]["id"]
|
|
||||||
end
|
|
||||||
|
|
||||||
with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do
|
|
||||||
object = %{
|
object = %{
|
||||||
"actor" => follow_activity.actor,
|
"actor" => follow_activity.actor,
|
||||||
"object" => follow_activity.data["object"],
|
"object" => follow_activity.data["object"],
|
||||||
|
@ -485,14 +525,7 @@ def prepare_outgoing(%{"type" => "Accept"} = data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare_outgoing(%{"type" => "Reject"} = data) do
|
def prepare_outgoing(%{"type" => "Reject"} = data) do
|
||||||
follow_activity_id =
|
with follow_activity <- Activity.normalize(data["object"]) do
|
||||||
if is_binary(data["object"]) do
|
|
||||||
data["object"]
|
|
||||||
else
|
|
||||||
data["object"]["id"]
|
|
||||||
end
|
|
||||||
|
|
||||||
with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do
|
|
||||||
object = %{
|
object = %{
|
||||||
"actor" => follow_activity.actor,
|
"actor" => follow_activity.actor,
|
||||||
"object" => follow_activity.data["object"],
|
"object" => follow_activity.data["object"],
|
||||||
|
|
|
@ -128,7 +128,7 @@ def lazy_put_object_defaults(map, activity \\ %{}) do
|
||||||
Inserts a full object if it is contained in an activity.
|
Inserts a full object if it is contained in an activity.
|
||||||
"""
|
"""
|
||||||
def insert_full_object(%{"object" => %{"type" => type} = object_data})
|
def insert_full_object(%{"object" => %{"type" => type} = object_data})
|
||||||
when is_map(object_data) and type in ["Note"] do
|
when is_map(object_data) and type in ["Article", "Note"] do
|
||||||
with {:ok, _} <- Object.create(object_data) do
|
with {:ok, _} <- Object.create(object_data) do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,7 +12,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
def render("user.json", %{user: user}) do
|
def render("user.json", %{user: user}) do
|
||||||
{:ok, user} = WebFinger.ensure_keys_present(user)
|
{:ok, user} = WebFinger.ensure_keys_present(user)
|
||||||
{:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"])
|
{:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"])
|
||||||
public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
|
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
|
||||||
public_key = :public_key.pem_encode([public_key])
|
public_key = :public_key.pem_encode([public_key])
|
||||||
|
|
||||||
%{
|
%{
|
||||||
|
@ -98,9 +98,6 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do
|
||||||
info = User.user_info(user)
|
info = User.user_info(user)
|
||||||
|
|
||||||
params = %{
|
params = %{
|
||||||
"type" => ["Create", "Announce"],
|
|
||||||
"actor_id" => user.ap_id,
|
|
||||||
"whole_db" => true,
|
|
||||||
"limit" => "10"
|
"limit" => "10"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,10 +108,8 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do
|
||||||
params
|
params
|
||||||
end
|
end
|
||||||
|
|
||||||
activities = ActivityPub.fetch_public_activities(params)
|
activities = ActivityPub.fetch_user_activities(user, nil, params)
|
||||||
min_id = Enum.at(activities, 0).id
|
min_id = Enum.at(Enum.reverse(activities), 0).id
|
||||||
|
|
||||||
activities = Enum.reverse(activities)
|
|
||||||
max_id = Enum.at(activities, 0).id
|
max_id = Enum.at(activities, 0).id
|
||||||
|
|
||||||
collection =
|
collection =
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
|
|
||||||
def delete(activity_id, user) do
|
def delete(activity_id, user) do
|
||||||
with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id),
|
with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id),
|
||||||
%Object{} = object <- Object.get_by_ap_id(object_id),
|
%Object{} = object <- Object.normalize(object_id),
|
||||||
true <- user.info["is_moderator"] || user.ap_id == object.data["actor"],
|
true <- user.info["is_moderator"] || user.ap_id == object.data["actor"],
|
||||||
{:ok, delete} <- ActivityPub.delete(object) do
|
{:ok, delete} <- ActivityPub.delete(object) do
|
||||||
{:ok, delete}
|
{:ok, delete}
|
||||||
|
@ -16,7 +16,7 @@ def delete(activity_id, user) do
|
||||||
|
|
||||||
def repeat(id_or_ap_id, user) do
|
def repeat(id_or_ap_id, user) do
|
||||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||||
object <- Object.get_by_ap_id(activity.data["object"]["id"]) do
|
object <- Object.normalize(activity.data["object"]["id"]) do
|
||||||
ActivityPub.announce(user, object)
|
ActivityPub.announce(user, object)
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -26,7 +26,7 @@ def repeat(id_or_ap_id, user) do
|
||||||
|
|
||||||
def unrepeat(id_or_ap_id, user) do
|
def unrepeat(id_or_ap_id, user) do
|
||||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||||
object <- Object.get_by_ap_id(activity.data["object"]["id"]) do
|
object <- Object.normalize(activity.data["object"]["id"]) do
|
||||||
ActivityPub.unannounce(user, object)
|
ActivityPub.unannounce(user, object)
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -37,7 +37,7 @@ def unrepeat(id_or_ap_id, user) do
|
||||||
def favorite(id_or_ap_id, user) do
|
def favorite(id_or_ap_id, user) do
|
||||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||||
false <- activity.data["actor"] == user.ap_id,
|
false <- activity.data["actor"] == user.ap_id,
|
||||||
object <- Object.get_by_ap_id(activity.data["object"]["id"]) do
|
object <- Object.normalize(activity.data["object"]["id"]) do
|
||||||
ActivityPub.like(user, object)
|
ActivityPub.like(user, object)
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -48,7 +48,7 @@ def favorite(id_or_ap_id, user) do
|
||||||
def unfavorite(id_or_ap_id, user) do
|
def unfavorite(id_or_ap_id, user) do
|
||||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||||
false <- activity.data["actor"] == user.ap_id,
|
false <- activity.data["actor"] == user.ap_id,
|
||||||
object <- Object.get_by_ap_id(activity.data["object"]["id"]) do
|
object <- Object.normalize(activity.data["object"]["id"]) do
|
||||||
ActivityPub.unlike(user, object)
|
ActivityPub.unlike(user, object)
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
|
|
|
@ -35,7 +35,8 @@ defmodule Pleroma.Web.Endpoint do
|
||||||
parsers: [:urlencoded, :multipart, :json],
|
parsers: [:urlencoded, :multipart, :json],
|
||||||
pass: ["*/*"],
|
pass: ["*/*"],
|
||||||
json_decoder: Jason,
|
json_decoder: Jason,
|
||||||
length: Application.get_env(:pleroma, :instance) |> Keyword.get(:upload_limit)
|
length: Application.get_env(:pleroma, :instance) |> Keyword.get(:upload_limit),
|
||||||
|
body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(Plug.MethodOverride)
|
plug(Plug.MethodOverride)
|
||||||
|
|
|
@ -95,7 +95,7 @@ def handle(:incoming_ap_doc, params) do
|
||||||
params = Utils.normalize_params(params)
|
params = Utils.normalize_params(params)
|
||||||
|
|
||||||
with {:ok, _user} <- ap_enabled_actor(params["actor"]),
|
with {:ok, _user} <- ap_enabled_actor(params["actor"]),
|
||||||
nil <- Activity.get_by_ap_id(params["id"]),
|
nil <- Activity.normalize(params["id"]),
|
||||||
{:ok, _activity} <- Transmogrifier.handle_incoming(params) do
|
{:ok, _activity} <- Transmogrifier.handle_incoming(params) do
|
||||||
else
|
else
|
||||||
%Activity{} ->
|
%Activity{} ->
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
alias Pleroma.{Repo, Activity, User, Notification, Stats}
|
alias Pleroma.{Repo, Object, Activity, User, Notification, Stats}
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView, ListView}
|
alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView, ListView}
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
@ -11,6 +11,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||||
|
|
||||||
action_fallback(:errors)
|
action_fallback(:errors)
|
||||||
|
|
||||||
def create_app(conn, params) do
|
def create_app(conn, params) do
|
||||||
|
@ -125,7 +127,7 @@ def masto_instance(conn, _params) do
|
||||||
response = %{
|
response = %{
|
||||||
uri: Web.base_url(),
|
uri: Web.base_url(),
|
||||||
title: Keyword.get(@instance, :name),
|
title: Keyword.get(@instance, :name),
|
||||||
description: "A Pleroma instance, an alternative fediverse server",
|
description: Keyword.get(@instance, :description),
|
||||||
version: "#{@mastodon_api_level} (compatible; #{Keyword.get(@instance, :version)})",
|
version: "#{@mastodon_api_level} (compatible; #{Keyword.get(@instance, :version)})",
|
||||||
email: Keyword.get(@instance, :email),
|
email: Keyword.get(@instance, :email),
|
||||||
urls: %{
|
urls: %{
|
||||||
|
@ -428,16 +430,43 @@ def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
render(conn, AccountView, "relationships.json", %{user: user, targets: targets})
|
render(conn, AccountView, "relationships.json", %{user: user, targets: targets})
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload(%{assigns: %{user: _}} = conn, %{"file" => file}) do
|
def update_media(%{assigns: %{user: _}} = conn, data) do
|
||||||
with {:ok, object} <- ActivityPub.upload(file) do
|
with %Object{} = object <- Repo.get(Object, data["id"]),
|
||||||
|
true <- is_binary(data["description"]),
|
||||||
|
description <- data["description"] do
|
||||||
|
new_data = %{object.data | "name" => description}
|
||||||
|
|
||||||
|
change = Object.change(object, %{data: new_data})
|
||||||
|
{:ok, media_obj} = Repo.update(change)
|
||||||
|
|
||||||
data =
|
data =
|
||||||
object.data
|
new_data
|
||||||
|> Map.put("id", object.id)
|
|> Map.put("id", object.id)
|
||||||
|
|
||||||
render(conn, StatusView, "attachment.json", %{attachment: data})
|
render(conn, StatusView, "attachment.json", %{attachment: data})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def upload(%{assigns: %{user: _}} = conn, %{"file" => file} = data) do
|
||||||
|
with {:ok, object} <- ActivityPub.upload(file) do
|
||||||
|
objdata =
|
||||||
|
if Map.has_key?(data, "description") do
|
||||||
|
Map.put(object.data, "name", data["description"])
|
||||||
|
else
|
||||||
|
object.data
|
||||||
|
end
|
||||||
|
|
||||||
|
change = Object.change(object, %{data: objdata})
|
||||||
|
{:ok, object} = Repo.update(change)
|
||||||
|
|
||||||
|
objdata =
|
||||||
|
objdata
|
||||||
|
|> Map.put("id", object.id)
|
||||||
|
|
||||||
|
render(conn, StatusView, "attachment.json", %{attachment: objdata})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def favourited_by(conn, %{"id" => id}) do
|
def favourited_by(conn, %{"id" => id}) do
|
||||||
with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do
|
with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do
|
||||||
q = from(u in User, where: u.ap_id in ^likes)
|
q = from(u in User, where: u.ap_id in ^likes)
|
||||||
|
@ -873,7 +902,7 @@ def index(%{assigns: %{user: user}} = conn, _params) do
|
||||||
},
|
},
|
||||||
compose: %{
|
compose: %{
|
||||||
me: "#{user.id}",
|
me: "#{user.id}",
|
||||||
default_privacy: "public",
|
default_privacy: user.info["default_scope"] || "public",
|
||||||
default_sensitive: false
|
default_sensitive: false
|
||||||
},
|
},
|
||||||
media_attachments: %{
|
media_attachments: %{
|
||||||
|
@ -1070,4 +1099,38 @@ def errors(conn, _) do
|
||||||
|> put_status(500)
|
|> put_status(500)
|
||||||
|> json("Something went wrong")
|
|> json("Something went wrong")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@suggestions Application.get_env(:pleroma, :suggestions)
|
||||||
|
|
||||||
|
def suggestions(%{assigns: %{user: user}} = conn, _) do
|
||||||
|
if Keyword.get(@suggestions, :enabled, false) do
|
||||||
|
api = Keyword.get(@suggestions, :third_party_engine, "")
|
||||||
|
timeout = Keyword.get(@suggestions, :timeout, 5000)
|
||||||
|
|
||||||
|
host =
|
||||||
|
Application.get_env(:pleroma, Pleroma.Web.Endpoint)
|
||||||
|
|> Keyword.get(:url)
|
||||||
|
|> Keyword.get(:host)
|
||||||
|
|
||||||
|
user = user.nickname
|
||||||
|
url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user)
|
||||||
|
|
||||||
|
with {:ok, %{status_code: 200, body: body}} <-
|
||||||
|
@httpoison.get(url, [], timeout: timeout, recv_timeout: timeout),
|
||||||
|
{:ok, data} <- Jason.decode(body) do
|
||||||
|
data2 =
|
||||||
|
Enum.slice(data, 0, 40)
|
||||||
|
|> Enum.map(fn x ->
|
||||||
|
Map.put(x, "id", User.get_or_fetch(x["acct"]).id)
|
||||||
|
end)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> json(data2)
|
||||||
|
else
|
||||||
|
e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
json(conn, [])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,6 +14,18 @@ def render("account.json", %{user: user}) do
|
||||||
header = User.banner_url(user) |> MediaProxy.url()
|
header = User.banner_url(user) |> MediaProxy.url()
|
||||||
user_info = User.user_info(user)
|
user_info = User.user_info(user)
|
||||||
|
|
||||||
|
emojis =
|
||||||
|
(user.info["source_data"]["tag"] || [])
|
||||||
|
|> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
|
||||||
|
|> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
|
||||||
|
%{
|
||||||
|
"shortcode" => String.trim(name, ":"),
|
||||||
|
"url" => MediaProxy.url(url),
|
||||||
|
"static_url" => MediaProxy.url(url),
|
||||||
|
"visible_in_picker" => false
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
id: to_string(user.id),
|
id: to_string(user.id),
|
||||||
username: hd(String.split(user.nickname, "@")),
|
username: hd(String.split(user.nickname, "@")),
|
||||||
|
@ -30,7 +42,7 @@ def render("account.json", %{user: user}) do
|
||||||
avatar_static: image,
|
avatar_static: image,
|
||||||
header: header,
|
header: header,
|
||||||
header_static: header,
|
header_static: header,
|
||||||
emojis: [],
|
emojis: emojis,
|
||||||
fields: [],
|
fields: [],
|
||||||
source: %{
|
source: %{
|
||||||
note: "",
|
note: "",
|
||||||
|
|
|
@ -54,8 +54,7 @@ def render(
|
||||||
%{
|
%{
|
||||||
id: to_string(activity.id),
|
id: to_string(activity.id),
|
||||||
uri: object,
|
uri: object,
|
||||||
# TODO: This might be wrong, check with mastodon.
|
url: object,
|
||||||
url: nil,
|
|
||||||
account: AccountView.render("account.json", %{user: user}),
|
account: AccountView.render("account.json", %{user: user}),
|
||||||
in_reply_to_id: nil,
|
in_reply_to_id: nil,
|
||||||
in_reply_to_account_id: nil,
|
in_reply_to_account_id: nil,
|
||||||
|
@ -128,7 +127,7 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
||||||
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),
|
||||||
reblog: nil,
|
reblog: nil,
|
||||||
content: HtmlSanitizeEx.basic_html(object["content"]),
|
content: render_content(object),
|
||||||
created_at: created_at,
|
created_at: created_at,
|
||||||
reblogs_count: announcement_count,
|
reblogs_count: announcement_count,
|
||||||
favourites_count: like_count,
|
favourites_count: like_count,
|
||||||
|
@ -170,7 +169,8 @@ def render("attachment.json", %{attachment: attachment}) do
|
||||||
remote_url: href,
|
remote_url: href,
|
||||||
preview_url: MediaProxy.url(href),
|
preview_url: MediaProxy.url(href),
|
||||||
text_url: href,
|
text_url: href,
|
||||||
type: type
|
type: type,
|
||||||
|
description: attachment["name"]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -207,4 +207,21 @@ def get_visibility(object) do
|
||||||
"direct"
|
"direct"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_content(%{"type" => "Article"} = object) do
|
||||||
|
summary = object["name"]
|
||||||
|
|
||||||
|
content =
|
||||||
|
if !!summary and summary != "" do
|
||||||
|
"<p><a href=\"#{object["url"]}\">#{summary}</a></p>#{object["content"]}"
|
||||||
|
else
|
||||||
|
object["content"]
|
||||||
|
end
|
||||||
|
|
||||||
|
HtmlSanitizeEx.basic_html(content)
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_content(object) do
|
||||||
|
HtmlSanitizeEx.basic_html(object["content"])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,8 +4,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
||||||
alias Pleroma.Stats
|
alias Pleroma.Stats
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
|
|
||||||
@instance Application.get_env(:pleroma, :instance)
|
|
||||||
|
|
||||||
def schemas(conn, _params) do
|
def schemas(conn, _params) do
|
||||||
response = %{
|
response = %{
|
||||||
links: [
|
links: [
|
||||||
|
@ -21,20 +19,23 @@ def schemas(conn, _params) do
|
||||||
|
|
||||||
# Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json
|
# Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json
|
||||||
def nodeinfo(conn, %{"version" => "2.0"}) do
|
def nodeinfo(conn, %{"version" => "2.0"}) do
|
||||||
|
instance = Application.get_env(:pleroma, :instance)
|
||||||
|
media_proxy = Application.get_env(:pleroma, :media_proxy)
|
||||||
|
suggestions = Application.get_env(:pleroma, :suggestions)
|
||||||
stats = Stats.get_stats()
|
stats = Stats.get_stats()
|
||||||
|
|
||||||
response = %{
|
response = %{
|
||||||
version: "2.0",
|
version: "2.0",
|
||||||
software: %{
|
software: %{
|
||||||
name: "pleroma",
|
name: "pleroma",
|
||||||
version: Keyword.get(@instance, :version)
|
version: Keyword.get(instance, :version)
|
||||||
},
|
},
|
||||||
protocols: ["ostatus", "activitypub"],
|
protocols: ["ostatus", "activitypub"],
|
||||||
services: %{
|
services: %{
|
||||||
inbound: [],
|
inbound: [],
|
||||||
outbound: []
|
outbound: []
|
||||||
},
|
},
|
||||||
openRegistrations: Keyword.get(@instance, :registrations_open),
|
openRegistrations: Keyword.get(instance, :registrations_open),
|
||||||
usage: %{
|
usage: %{
|
||||||
users: %{
|
users: %{
|
||||||
total: stats.user_count || 0
|
total: stats.user_count || 0
|
||||||
|
@ -42,7 +43,16 @@ def nodeinfo(conn, %{"version" => "2.0"}) do
|
||||||
localPosts: stats.status_count || 0
|
localPosts: stats.status_count || 0
|
||||||
},
|
},
|
||||||
metadata: %{
|
metadata: %{
|
||||||
nodeName: Keyword.get(@instance, :name)
|
nodeName: Keyword.get(instance, :name),
|
||||||
|
nodeDescription: Keyword.get(instance, :description),
|
||||||
|
mediaProxy: Keyword.get(media_proxy, :enabled),
|
||||||
|
private: !Keyword.get(instance, :public, true),
|
||||||
|
suggestions: %{
|
||||||
|
enabled: Keyword.get(suggestions, :enabled, false),
|
||||||
|
thirdPartyEngine: Keyword.get(suggestions, :third_party_engine, ""),
|
||||||
|
timeout: Keyword.get(suggestions, :timeout, 5000),
|
||||||
|
web: Keyword.get(suggestions, :web, "")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -246,7 +246,7 @@ def to_simple_form(
|
||||||
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
||||||
|
|
||||||
mentions = (activity.recipients || []) |> get_mentions
|
mentions = (activity.recipients || []) |> get_mentions
|
||||||
follow_activity = Activity.get_by_ap_id(follow_activity["id"])
|
follow_activity = Activity.normalize(follow_activity)
|
||||||
|
|
||||||
[
|
[
|
||||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
||||||
|
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.OStatus.DeleteHandler do
|
||||||
|
|
||||||
def handle_delete(entry, _doc \\ nil) do
|
def handle_delete(entry, _doc \\ nil) do
|
||||||
with id <- XML.string_from_xpath("//id", entry),
|
with id <- XML.string_from_xpath("//id", entry),
|
||||||
object when not is_nil(object) <- Object.get_by_ap_id(id),
|
%Object{} = object <- Object.normalize(id),
|
||||||
{:ok, delete} <- ActivityPub.delete(object, false) do
|
{:ok, delete} <- ActivityPub.delete(object, false) do
|
||||||
delete
|
delete
|
||||||
end
|
end
|
||||||
|
|
|
@ -89,7 +89,7 @@ def handle_incoming(xml_string) do
|
||||||
|
|
||||||
def make_share(entry, doc, retweeted_activity) do
|
def make_share(entry, doc, retweeted_activity) do
|
||||||
with {:ok, actor} <- find_make_or_update_user(doc),
|
with {:ok, actor} <- find_make_or_update_user(doc),
|
||||||
%Object{} = object <- Object.get_by_ap_id(retweeted_activity.data["object"]["id"]),
|
%Object{} = object <- Object.normalize(retweeted_activity.data["object"]),
|
||||||
id when not is_nil(id) <- string_from_xpath("/entry/id", entry),
|
id when not is_nil(id) <- string_from_xpath("/entry/id", entry),
|
||||||
{:ok, activity, _object} = ActivityPub.announce(actor, object, id, false) do
|
{:ok, activity, _object} = ActivityPub.announce(actor, object, id, false) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
@ -107,7 +107,7 @@ def handle_share(entry, doc) do
|
||||||
|
|
||||||
def make_favorite(entry, doc, favorited_activity) do
|
def make_favorite(entry, doc, favorited_activity) do
|
||||||
with {:ok, actor} <- find_make_or_update_user(doc),
|
with {:ok, actor} <- find_make_or_update_user(doc),
|
||||||
%Object{} = object <- Object.get_by_ap_id(favorited_activity.data["object"]["id"]),
|
%Object{} = object <- Object.normalize(favorited_activity.data["object"]),
|
||||||
id when not is_nil(id) <- string_from_xpath("/entry/id", entry),
|
id when not is_nil(id) <- string_from_xpath("/entry/id", entry),
|
||||||
{:ok, activity, _object} = ActivityPub.like(actor, object, id, false) do
|
{:ok, activity, _object} = ActivityPub.like(actor, object, id, false) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.Web.{OStatus, Federator}
|
alias Pleroma.Web.{OStatus, Federator}
|
||||||
alias Pleroma.Web.XML
|
alias Pleroma.Web.XML
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectView
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPubController
|
alias Pleroma.Web.ActivityPub.ActivityPubController
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
|
||||||
|
@ -90,7 +91,7 @@ def object(conn, %{"uuid" => uuid}) do
|
||||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||||
case get_format(conn) do
|
case get_format(conn) do
|
||||||
"html" -> redirect(conn, to: "/notice/#{activity.id}")
|
"html" -> redirect(conn, to: "/notice/#{activity.id}")
|
||||||
_ -> represent_activity(conn, activity, user)
|
_ -> represent_activity(conn, nil, activity, user)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
{:public?, false} ->
|
{:public?, false} ->
|
||||||
|
@ -107,12 +108,12 @@ def object(conn, %{"uuid" => uuid}) do
|
||||||
|
|
||||||
def activity(conn, %{"uuid" => uuid}) do
|
def activity(conn, %{"uuid" => uuid}) do
|
||||||
with id <- o_status_url(conn, :activity, uuid),
|
with id <- o_status_url(conn, :activity, uuid),
|
||||||
{_, %Activity{} = activity} <- {:activity, Activity.get_by_ap_id(id)},
|
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
|
||||||
{_, true} <- {:public?, ActivityPub.is_public?(activity)},
|
{_, true} <- {:public?, ActivityPub.is_public?(activity)},
|
||||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||||
case get_format(conn) do
|
case format = get_format(conn) do
|
||||||
"html" -> redirect(conn, to: "/notice/#{activity.id}")
|
"html" -> redirect(conn, to: "/notice/#{activity.id}")
|
||||||
_ -> represent_activity(conn, activity, user)
|
_ -> represent_activity(conn, format, activity, user)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
{:public?, false} ->
|
{:public?, false} ->
|
||||||
|
@ -130,14 +131,14 @@ def notice(conn, %{"id" => id}) do
|
||||||
with {_, %Activity{} = activity} <- {:activity, Repo.get(Activity, id)},
|
with {_, %Activity{} = activity} <- {:activity, Repo.get(Activity, id)},
|
||||||
{_, true} <- {:public?, ActivityPub.is_public?(activity)},
|
{_, true} <- {:public?, ActivityPub.is_public?(activity)},
|
||||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||||
case get_format(conn) do
|
case format = get_format(conn) do
|
||||||
"html" ->
|
"html" ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("text/html")
|
|> put_resp_content_type("text/html")
|
||||||
|> send_file(200, "priv/static/index.html")
|
|> send_file(200, "priv/static/index.html")
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
represent_activity(conn, activity, user)
|
represent_activity(conn, format, activity, user)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
{:public?, false} ->
|
{:public?, false} ->
|
||||||
|
@ -151,7 +152,13 @@ def notice(conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp represent_activity(conn, activity, user) do
|
defp represent_activity(conn, "activity+json", activity, user) do
|
||||||
|
conn
|
||||||
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|
|> json(ObjectView.render("object.json", %{object: activity}))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp represent_activity(conn, _, activity, user) do
|
||||||
response =
|
response =
|
||||||
activity
|
activity
|
||||||
|> ActivityRepresenter.to_simple_form(user, true)
|
|> ActivityRepresenter.to_simple_form(user, true)
|
||||||
|
|
|
@ -127,6 +127,7 @@ def user_fetcher(username) do
|
||||||
get("/notifications/:id", MastodonAPIController, :get_notification)
|
get("/notifications/:id", MastodonAPIController, :get_notification)
|
||||||
|
|
||||||
post("/media", MastodonAPIController, :upload)
|
post("/media", MastodonAPIController, :upload)
|
||||||
|
put("/media/:id", MastodonAPIController, :update_media)
|
||||||
|
|
||||||
get("/lists", MastodonAPIController, :get_lists)
|
get("/lists", MastodonAPIController, :get_lists)
|
||||||
get("/lists/:id", MastodonAPIController, :get_list)
|
get("/lists/:id", MastodonAPIController, :get_list)
|
||||||
|
@ -140,6 +141,8 @@ def user_fetcher(username) do
|
||||||
get("/domain_blocks", MastodonAPIController, :domain_blocks)
|
get("/domain_blocks", MastodonAPIController, :domain_blocks)
|
||||||
post("/domain_blocks", MastodonAPIController, :block_domain)
|
post("/domain_blocks", MastodonAPIController, :block_domain)
|
||||||
delete("/domain_blocks", MastodonAPIController, :unblock_domain)
|
delete("/domain_blocks", MastodonAPIController, :unblock_domain)
|
||||||
|
|
||||||
|
get("/suggestions", MastodonAPIController, :suggestions)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/web", Pleroma.Web.MastodonAPI do
|
scope "/api/web", Pleroma.Web.MastodonAPI do
|
||||||
|
@ -201,9 +204,7 @@ def user_fetcher(username) do
|
||||||
get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status)
|
get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status)
|
||||||
get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation)
|
get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation)
|
||||||
|
|
||||||
if @registrations_open do
|
|
||||||
post("/account/register", TwitterAPI.Controller, :register)
|
post("/account/register", TwitterAPI.Controller, :register)
|
||||||
end
|
|
||||||
|
|
||||||
get("/search", TwitterAPI.Controller, :search)
|
get("/search", TwitterAPI.Controller, :search)
|
||||||
get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline)
|
get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline)
|
||||||
|
@ -355,6 +356,7 @@ def user_fetcher(username) do
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", Fallback do
|
scope "/", Fallback do
|
||||||
|
get("/registration/:token", RedirectController, :registration_page)
|
||||||
get("/*path", RedirectController, :redirector)
|
get("/*path", RedirectController, :redirector)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -369,4 +371,8 @@ def redirector(conn, _params) do
|
||||||
|> send_file(200, "priv/static/index.html")
|
|> send_file(200, "priv/static/index.html")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def registration_page(conn, params) do
|
||||||
|
redirector(conn, params)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -158,7 +158,7 @@ def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = ite
|
||||||
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
|
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
|
||||||
blocks = user.info["blocks"] || []
|
blocks = user.info["blocks"] || []
|
||||||
|
|
||||||
parent = Object.get_by_ap_id(item.data["object"])
|
parent = Object.normalize(item.data["object"])
|
||||||
|
|
||||||
unless is_nil(parent) or item.actor in blocks or parent.data["actor"] in blocks do
|
unless is_nil(parent) or item.actor in blocks or parent.data["actor"] in blocks do
|
||||||
send(socket.transport_pid, {:text, represent_update(item, user)})
|
send(socket.transport_pid, {:text, represent_update(item, user)})
|
||||||
|
|
|
@ -99,6 +99,10 @@ def do_remote_follow(conn, %{
|
||||||
conn
|
conn
|
||||||
|> render("followed.html", %{error: false})
|
|> render("followed.html", %{error: false})
|
||||||
else
|
else
|
||||||
|
# Was already following user
|
||||||
|
{:error, "Could not follow user:" <> _rest} ->
|
||||||
|
render(conn, "followed.html", %{error: false})
|
||||||
|
|
||||||
_e ->
|
_e ->
|
||||||
conn
|
conn
|
||||||
|> render("follow_login.html", %{
|
|> render("follow_login.html", %{
|
||||||
|
@ -117,6 +121,11 @@ def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}
|
||||||
conn
|
conn
|
||||||
|> render("followed.html", %{error: false})
|
|> render("followed.html", %{error: false})
|
||||||
else
|
else
|
||||||
|
# Was already following user
|
||||||
|
{:error, "Could not follow user:" <> _rest} ->
|
||||||
|
conn
|
||||||
|
|> render("followed.html", %{error: false})
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
Logger.debug("Remote follow failed with error #{inspect(e)}")
|
Logger.debug("Remote follow failed with error #{inspect(e)}")
|
||||||
|
|
||||||
|
@ -126,6 +135,8 @@ def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}
|
||||||
end
|
end
|
||||||
|
|
||||||
@instance Application.get_env(:pleroma, :instance)
|
@instance Application.get_env(:pleroma, :instance)
|
||||||
|
@instance_fe Application.get_env(:pleroma, :fe)
|
||||||
|
@instance_chat Application.get_env(:pleroma, :chat)
|
||||||
def config(conn, _params) do
|
def config(conn, _params) do
|
||||||
case get_format(conn) do
|
case get_format(conn) do
|
||||||
"xml" ->
|
"xml" ->
|
||||||
|
@ -148,9 +159,24 @@ def config(conn, _params) do
|
||||||
json(conn, %{
|
json(conn, %{
|
||||||
site: %{
|
site: %{
|
||||||
name: Keyword.get(@instance, :name),
|
name: Keyword.get(@instance, :name),
|
||||||
|
description: Keyword.get(@instance, :description),
|
||||||
server: Web.base_url(),
|
server: Web.base_url(),
|
||||||
textlimit: to_string(Keyword.get(@instance, :limit)),
|
textlimit: to_string(Keyword.get(@instance, :limit)),
|
||||||
closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1")
|
closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1"),
|
||||||
|
private: if(Keyword.get(@instance, :public, true), do: "0", else: "1"),
|
||||||
|
pleromafe: %{
|
||||||
|
theme: Keyword.get(@instance_fe, :theme),
|
||||||
|
background: Keyword.get(@instance_fe, :background),
|
||||||
|
logo: Keyword.get(@instance_fe, :logo),
|
||||||
|
redirectRootNoLogin: Keyword.get(@instance_fe, :redirect_root_no_login),
|
||||||
|
redirectRootLogin: Keyword.get(@instance_fe, :redirect_root_login),
|
||||||
|
chatDisabled: !Keyword.get(@instance_chat, :enabled),
|
||||||
|
showInstanceSpecificPanel: Keyword.get(@instance_fe, :show_instance_panel),
|
||||||
|
showWhoToFollowPanel: Keyword.get(@instance_fe, :show_who_to_follow_panel),
|
||||||
|
scopeOptionsEnabled: Keyword.get(@instance_fe, :scope_options_enabled),
|
||||||
|
whoToFollowProvider: Keyword.get(@instance_fe, :who_to_follow_provider),
|
||||||
|
whoToFollowLink: Keyword.get(@instance_fe, :who_to_follow_link)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
|
||||||
use Pleroma.Web.TwitterAPI.Representers.BaseRepresenter
|
use Pleroma.Web.TwitterAPI.Representers.BaseRepresenter
|
||||||
alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter
|
alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter
|
||||||
alias Pleroma.{Activity, User}
|
alias Pleroma.{Activity, User}
|
||||||
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView}
|
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView}
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
alias Pleroma.Formatter
|
alias Pleroma.Formatter
|
||||||
|
|
||||||
|
@ -164,14 +164,7 @@ def to_map(
|
||||||
|
|
||||||
tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags
|
tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags
|
||||||
|
|
||||||
summary = activity.data["object"]["summary"]
|
{summary, content} = ActivityView.render_content(object)
|
||||||
|
|
||||||
content =
|
|
||||||
if !!summary and summary != "" do
|
|
||||||
"<span>#{activity.data["object"]["summary"]}</span><br />#{content}</span>"
|
|
||||||
else
|
|
||||||
content
|
|
||||||
end
|
|
||||||
|
|
||||||
html =
|
html =
|
||||||
HtmlSanitizeEx.basic_html(content)
|
HtmlSanitizeEx.basic_html(content)
|
||||||
|
@ -198,7 +191,8 @@ def to_map(
|
||||||
"tags" => tags,
|
"tags" => tags,
|
||||||
"activity_type" => "post",
|
"activity_type" => "post",
|
||||||
"possibly_sensitive" => possibly_sensitive,
|
"possibly_sensitive" => possibly_sensitive,
|
||||||
"visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object)
|
"visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object),
|
||||||
|
"summary" => object["summary"]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
||||||
alias Pleroma.{User, Activity, Repo, Object}
|
alias Pleroma.{UserInviteToken, User, Activity, Repo, Object}
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.TwitterAPI.UserView
|
alias Pleroma.Web.TwitterAPI.UserView
|
||||||
alias Pleroma.Web.{OStatus, CommonAPI}
|
alias Pleroma.Web.{OStatus, CommonAPI}
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
|
@instance Application.get_env(:pleroma, :instance)
|
||||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||||
|
@registrations_open Keyword.get(@instance, :registrations_open)
|
||||||
|
|
||||||
def create_status(%User{} = user, %{"status" => _} = data) do
|
def create_status(%User{} = user, %{"status" => _} = data) do
|
||||||
CommonAPI.post(user, data)
|
CommonAPI.post(user, data)
|
||||||
|
@ -120,6 +122,8 @@ def upload(%Plug.Upload{} = file, format \\ "xml") do
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_user(params) do
|
def register_user(params) do
|
||||||
|
tokenString = params["token"]
|
||||||
|
|
||||||
params = %{
|
params = %{
|
||||||
nickname: params["nickname"],
|
nickname: params["nickname"],
|
||||||
name: params["fullname"],
|
name: params["fullname"],
|
||||||
|
@ -129,9 +133,18 @@ def register_user(params) do
|
||||||
password_confirmation: params["confirm"]
|
password_confirmation: params["confirm"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# no need to query DB if registration is open
|
||||||
|
token =
|
||||||
|
unless @registrations_open || is_nil(tokenString) do
|
||||||
|
Repo.get_by(UserInviteToken, %{token: tokenString})
|
||||||
|
end
|
||||||
|
|
||||||
|
cond do
|
||||||
|
@registrations_open || (!is_nil(token) && !token.used) ->
|
||||||
changeset = User.register_changeset(%User{}, params)
|
changeset = User.register_changeset(%User{}, params)
|
||||||
|
|
||||||
with {:ok, user} <- Repo.insert(changeset) do
|
with {:ok, user} <- Repo.insert(changeset) do
|
||||||
|
!@registrations_open && UserInviteToken.mark_as_used(token.token)
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
{:error, changeset} ->
|
{:error, changeset} ->
|
||||||
|
@ -141,6 +154,13 @@ def register_user(params) do
|
||||||
|
|
||||||
{:error, %{error: errors}}
|
{:error, %{error: errors}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
!@registrations_open && is_nil(token) ->
|
||||||
|
{:error, "Invalid token"}
|
||||||
|
|
||||||
|
!@registrations_open && token.used ->
|
||||||
|
{:error, "Expired token"}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_by_id_or_nickname(id_or_nickname) do
|
def get_by_id_or_nickname(id_or_nickname) do
|
||||||
|
|
|
@ -431,6 +431,19 @@ def update_profile(%{assigns: %{user: user}} = conn, params) do
|
||||||
user
|
user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
user =
|
||||||
|
if default_scope = params["default_scope"] do
|
||||||
|
with new_info <- Map.put(user.info, "default_scope", default_scope),
|
||||||
|
change <- User.info_changeset(user, %{info: new_info}),
|
||||||
|
{:ok, user} <- User.update_and_set_cache(change) do
|
||||||
|
user
|
||||||
|
else
|
||||||
|
_e -> user
|
||||||
|
end
|
||||||
|
else
|
||||||
|
user
|
||||||
|
end
|
||||||
|
|
||||||
with changeset <- User.update_changeset(user, params),
|
with changeset <- User.update_changeset(user, params),
|
||||||
{:ok, user} <- User.update_and_set_cache(changeset) do
|
{:ok, user} <- User.update_and_set_cache(changeset) do
|
||||||
CommonAPI.update(user)
|
CommonAPI.update(user)
|
||||||
|
|
|
@ -228,15 +228,7 @@ def render(
|
||||||
|
|
||||||
tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags
|
tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags
|
||||||
|
|
||||||
summary = activity.data["object"]["summary"]
|
{summary, content} = render_content(object)
|
||||||
content = object["content"]
|
|
||||||
|
|
||||||
content =
|
|
||||||
if !!summary and summary != "" do
|
|
||||||
"<span>#{activity.data["object"]["summary"]}</span><br />#{content}</span>"
|
|
||||||
else
|
|
||||||
content
|
|
||||||
end
|
|
||||||
|
|
||||||
html =
|
html =
|
||||||
HtmlSanitizeEx.basic_html(content)
|
HtmlSanitizeEx.basic_html(content)
|
||||||
|
@ -263,7 +255,41 @@ def render(
|
||||||
"tags" => tags,
|
"tags" => tags,
|
||||||
"activity_type" => "post",
|
"activity_type" => "post",
|
||||||
"possibly_sensitive" => possibly_sensitive,
|
"possibly_sensitive" => possibly_sensitive,
|
||||||
"visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object)
|
"visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object),
|
||||||
|
"summary" => summary
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_content(%{"type" => "Note"} = object) do
|
||||||
|
summary = object["summary"]
|
||||||
|
|
||||||
|
content =
|
||||||
|
if !!summary and summary != "" do
|
||||||
|
"<p>#{summary}</p>#{object["content"]}"
|
||||||
|
else
|
||||||
|
object["content"]
|
||||||
|
end
|
||||||
|
|
||||||
|
{summary, content}
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_content(%{"type" => "Article"} = object) do
|
||||||
|
summary = object["name"] || object["summary"]
|
||||||
|
|
||||||
|
content =
|
||||||
|
if !!summary and summary != "" do
|
||||||
|
"<p><a href=\"#{object["url"]}\">#{summary}</a></p>#{object["content"]}"
|
||||||
|
else
|
||||||
|
object["content"]
|
||||||
|
end
|
||||||
|
|
||||||
|
{summary, content}
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_content(object) do
|
||||||
|
summary = object["summary"] || "Unhandled activity type: #{object["type"]}"
|
||||||
|
content = "<p>#{summary}</p>#{object["content"]}"
|
||||||
|
|
||||||
|
{summary, content}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
defmodule Pleroma.Web.TwitterAPI.UserView do
|
defmodule Pleroma.Web.TwitterAPI.UserView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Formatter
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
|
|
||||||
|
@ -28,9 +29,19 @@ def render("user.json", %{user: user = %User{}} = assigns) do
|
||||||
|
|
||||||
user_info = User.get_cached_user_info(user)
|
user_info = User.get_cached_user_info(user)
|
||||||
|
|
||||||
|
emoji =
|
||||||
|
(user.info["source_data"]["tag"] || [])
|
||||||
|
|> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
|
||||||
|
|> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
|
||||||
|
{String.trim(name, ":"), url}
|
||||||
|
end)
|
||||||
|
|
||||||
|
bio = HtmlSanitizeEx.strip_tags(user.bio)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
|
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
|
||||||
"description" => HtmlSanitizeEx.strip_tags(user.bio),
|
"description" => bio,
|
||||||
|
"description_html" => bio |> Formatter.emojify(emoji),
|
||||||
"favourites_count" => 0,
|
"favourites_count" => 0,
|
||||||
"followers_count" => user_info[:follower_count],
|
"followers_count" => user_info[:follower_count],
|
||||||
"following" => following,
|
"following" => following,
|
||||||
|
@ -39,6 +50,7 @@ def render("user.json", %{user: user = %User{}} = assigns) do
|
||||||
"friends_count" => user_info[:following_count],
|
"friends_count" => user_info[:following_count],
|
||||||
"id" => user.id,
|
"id" => user.id,
|
||||||
"name" => user.name,
|
"name" => user.name,
|
||||||
|
"name_html" => HtmlSanitizeEx.strip_tags(user.name) |> Formatter.emojify(emoji),
|
||||||
"profile_image_url" => image,
|
"profile_image_url" => image,
|
||||||
"profile_image_url_https" => image,
|
"profile_image_url_https" => image,
|
||||||
"profile_image_url_profile_size" => image,
|
"profile_image_url_profile_size" => image,
|
||||||
|
@ -52,7 +64,8 @@ def render("user.json", %{user: user = %User{}} = assigns) do
|
||||||
"cover_photo" => User.banner_url(user) |> MediaProxy.url(),
|
"cover_photo" => User.banner_url(user) |> MediaProxy.url(),
|
||||||
"background_image" => image_url(user.info["background"]) |> MediaProxy.url(),
|
"background_image" => image_url(user.info["background"]) |> MediaProxy.url(),
|
||||||
"is_local" => user.local,
|
"is_local" => user.local,
|
||||||
"locked" => !!user.info["locked"]
|
"locked" => !!user.info["locked"],
|
||||||
|
"default_scope" => user.info["default_scope"] || "public"
|
||||||
}
|
}
|
||||||
|
|
||||||
if assigns[:token] do
|
if assigns[:token] do
|
||||||
|
|
1
mix.exs
1
mix.exs
|
@ -45,6 +45,7 @@ defp deps do
|
||||||
{:cachex, "~> 3.0.2"},
|
{:cachex, "~> 3.0.2"},
|
||||||
{:httpoison, "~> 1.2.0"},
|
{:httpoison, "~> 1.2.0"},
|
||||||
{:jason, "~> 1.0"},
|
{:jason, "~> 1.0"},
|
||||||
|
{:mogrify, "~> 0.6.1"}
|
||||||
{:ex_machina, "~> 2.2", only: :test},
|
{:ex_machina, "~> 2.2", only: :test},
|
||||||
{:credo, "~> 0.9.3", only: [:dev, :test]},
|
{:credo, "~> 0.9.3", only: [:dev, :test]},
|
||||||
{:mock, "~> 0.3.1", only: :test}
|
{:mock, "~> 0.3.1", only: :test}
|
||||||
|
|
1
mix.lock
1
mix.lock
|
@ -25,6 +25,7 @@
|
||||||
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
|
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
|
||||||
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
|
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
|
||||||
"mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
"mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
|
||||||
"parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"},
|
"parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"},
|
||||||
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"},
|
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"},
|
||||||
"phoenix": {:hex, :phoenix, "1.3.2", "2a00d751f51670ea6bc3f2ba4e6eb27ecb8a2c71e7978d9cd3e5de5ccf7378bd", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
"phoenix": {:hex, :phoenix, "1.3.2", "2a00d751f51670ea6bc3f2ba4e6eb27ecb8a2c71e7978d9cd3e5de5ccf7378bd", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateUserInviteTokens do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:user_invite_tokens) do
|
||||||
|
add :token, :string
|
||||||
|
add :used, :boolean, default: false
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1 +1 @@
|
||||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.5d0189b6f119febde070b703869bbd06.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.f2341edd686e54ee9b4a.js></script><script type=text/javascript src=/static/js/vendor.a93310d51acbd9480094.js></script><script type=text/javascript src=/static/js/app.de965bb2a0a8bffbeafa.js></script></body></html>
|
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.4e316dc76ab907cb78bb88b978ce04e2.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.212bbb8f66cdc9a7eebf.js></script><script type=text/javascript src=/static/js/vendor.da712c56a19114013b34.js></script><script type=text/javascript src=/static/js/app.9d52d5cd0ef719b7db59.js></script></body></html>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,5 +1,5 @@
|
||||||
CACHE MANIFEST
|
CACHE MANIFEST
|
||||||
#ver:2018-4-9 21:57:37
|
#ver:2018-8-12 18:01:32
|
||||||
#plugin:4.8.4
|
#plugin:4.8.4
|
||||||
|
|
||||||
CACHE:
|
CACHE:
|
||||||
|
@ -13,33 +13,46 @@ CACHE:
|
||||||
/packs/features/home_timeline.js
|
/packs/features/home_timeline.js
|
||||||
/packs/features/public_timeline.js
|
/packs/features/public_timeline.js
|
||||||
/packs/features/community_timeline.js
|
/packs/features/community_timeline.js
|
||||||
/packs/features/favourited_statuses.js
|
/packs/features/direct_timeline.js
|
||||||
/packs/features/list_timeline.js
|
/packs/features/pinned_statuses.js
|
||||||
|
/packs/features/domain_blocks.js
|
||||||
/packs/features/following.js
|
/packs/features/following.js
|
||||||
/packs/features/followers.js
|
/packs/features/followers.js
|
||||||
|
/packs/features/favourited_statuses.js
|
||||||
|
/packs/features/list_timeline.js
|
||||||
|
/packs/features/account_gallery.js
|
||||||
/packs/features/hashtag_timeline.js
|
/packs/features/hashtag_timeline.js
|
||||||
/packs/features/status.js
|
/packs/features/status.js
|
||||||
/packs/features/account_gallery.js
|
/packs/features/lists.js
|
||||||
/packs/features/blocks.js
|
/packs/modals/report_modal.js
|
||||||
|
/packs/features/getting_started.js
|
||||||
/packs/features/follow_requests.js
|
/packs/features/follow_requests.js
|
||||||
|
/packs/features/mutes.js
|
||||||
|
/packs/features/blocks.js
|
||||||
/packs/features/reblogs.js
|
/packs/features/reblogs.js
|
||||||
/packs/features/favourites.js
|
/packs/features/favourites.js
|
||||||
/packs/features/getting_started.js
|
|
||||||
/packs/features/keyboard_shortcuts.js
|
/packs/features/keyboard_shortcuts.js
|
||||||
|
/packs/modals/mute_modal.js
|
||||||
/packs/features/generic_not_found.js
|
/packs/features/generic_not_found.js
|
||||||
/packs/features/list_editor.js
|
/packs/features/list_editor.js
|
||||||
|
/packs/modals/embed_modal.js
|
||||||
/packs/status/media_gallery.js
|
/packs/status/media_gallery.js
|
||||||
|
/packs/containers/media_container.js
|
||||||
/packs/share.js
|
/packs/share.js
|
||||||
/packs/application.js
|
/packs/application.js
|
||||||
/packs/about.js
|
/packs/about.js
|
||||||
/packs/public.js
|
|
||||||
/packs/mailer.js
|
/packs/mailer.js
|
||||||
|
/packs/mastodon-light.js
|
||||||
|
/packs/contrast.js
|
||||||
/packs/default.js
|
/packs/default.js
|
||||||
|
/packs/public.js
|
||||||
/packs/admin.js
|
/packs/admin.js
|
||||||
/packs/common.js
|
/packs/common.js
|
||||||
/packs/common.css
|
/packs/common.css
|
||||||
/packs/mailer.css
|
/packs/mailer.css
|
||||||
/packs/default.css
|
/packs/default.css
|
||||||
|
/packs/contrast.css
|
||||||
|
/packs/mastodon-light.css
|
||||||
/packs/manifest.json
|
/packs/manifest.json
|
||||||
|
|
||||||
NETWORK:
|
NETWORK:
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/packs/containers/media_container.js
Normal file
BIN
priv/static/packs/containers/media_container.js
Normal file
Binary file not shown.
BIN
priv/static/packs/containers/media_container.js.map
Normal file
BIN
priv/static/packs/containers/media_container.js.map
Normal file
Binary file not shown.
BIN
priv/static/packs/contrast.css
Normal file
BIN
priv/static/packs/contrast.css
Normal file
Binary file not shown.
1
priv/static/packs/contrast.css.map
Normal file
1
priv/static/packs/contrast.css.map
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"sources":[],"names":[],"mappings":"","file":"contrast.css","sourceRoot":""}
|
BIN
priv/static/packs/contrast.js
Normal file
BIN
priv/static/packs/contrast.js
Normal file
Binary file not shown.
BIN
priv/static/packs/contrast.js.map
Normal file
BIN
priv/static/packs/contrast.js.map
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 40 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 11 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 17 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 11 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 8.3 KiB |
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.
BIN
priv/static/packs/features/direct_timeline.js
Normal file
BIN
priv/static/packs/features/direct_timeline.js
Normal file
Binary file not shown.
BIN
priv/static/packs/features/direct_timeline.js.map
Normal file
BIN
priv/static/packs/features/direct_timeline.js.map
Normal file
Binary file not shown.
BIN
priv/static/packs/features/domain_blocks.js
Normal file
BIN
priv/static/packs/features/domain_blocks.js
Normal file
Binary file not shown.
BIN
priv/static/packs/features/domain_blocks.js.map
Normal file
BIN
priv/static/packs/features/domain_blocks.js.map
Normal file
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