Format the code.
This commit is contained in:
parent
480932c8e5
commit
4afbef39f4
111 changed files with 4912 additions and 2769 deletions
|
@ -6,14 +6,11 @@
|
|||
use Mix.Config
|
||||
|
||||
# General application configuration
|
||||
config :pleroma,
|
||||
ecto_repos: [Pleroma.Repo]
|
||||
config :pleroma, ecto_repos: [Pleroma.Repo]
|
||||
|
||||
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"
|
||||
|
||||
# Configures the endpoint
|
||||
config :pleroma, Pleroma.Web.Endpoint,
|
||||
|
@ -21,8 +18,7 @@
|
|||
protocol: "https",
|
||||
secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl",
|
||||
render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
|
||||
pubsub: [name: Pleroma.PubSub,
|
||||
adapter: Phoenix.PubSub.PG2]
|
||||
pubsub: [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]
|
||||
|
||||
# Configures Elixir's Logger
|
||||
config :logger, :console,
|
||||
|
@ -38,15 +34,15 @@
|
|||
config :pleroma, :ostatus, Pleroma.Web.OStatus
|
||||
config :pleroma, :httpoison, Pleroma.HTTP
|
||||
|
||||
version = with {version, 0} <- System.cmd("git", ["rev-parse", "HEAD"]) do
|
||||
"Pleroma #{Mix.Project.config[:version]} #{String.trim(version)}"
|
||||
else
|
||||
_ -> "Pleroma #{Mix.Project.config[:version]} dev"
|
||||
end
|
||||
version =
|
||||
with {version, 0} <- System.cmd("git", ["rev-parse", "HEAD"]) do
|
||||
"Pleroma #{Mix.Project.config()[:version]} #{String.trim(version)}"
|
||||
else
|
||||
_ -> "Pleroma #{Mix.Project.config()[:version]} dev"
|
||||
end
|
||||
|
||||
# Configures http settings, upstream proxy etc.
|
||||
config :pleroma, :http,
|
||||
proxy_url: nil
|
||||
config :pleroma, :http, proxy_url: nil
|
||||
|
||||
config :pleroma, :instance,
|
||||
version: version,
|
||||
|
@ -59,16 +55,15 @@
|
|||
config :pleroma, :media_proxy,
|
||||
enabled: false,
|
||||
redirect_on_failure: true
|
||||
#base_url: "https://cache.pleroma.social"
|
||||
|
||||
config :pleroma, :chat,
|
||||
enabled: true
|
||||
# base_url: "https://cache.pleroma.social"
|
||||
|
||||
config :pleroma, :chat, enabled: true
|
||||
|
||||
config :ecto, json_library: Jason
|
||||
|
||||
config :phoenix, :format_encoders,
|
||||
json: Jason
|
||||
config :phoenix, :format_encoders, json: Jason
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{Mix.env}.exs"
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
# watchers to your application. For example, we use it
|
||||
# with brunch.io to recompile .js and .css sources.
|
||||
config :pleroma, Pleroma.Web.Endpoint,
|
||||
http: [port: 4000, protocol_options: [max_request_line_length: 8192, max_header_value_length: 8192]],
|
||||
http: [
|
||||
port: 4000,
|
||||
protocol_options: [max_request_line_length: 8192, max_header_value_length: 8192]
|
||||
],
|
||||
protocol: "http",
|
||||
debug_errors: true,
|
||||
code_reloader: true,
|
||||
|
@ -49,5 +52,8 @@
|
|||
try do
|
||||
import_config "dev.secret.exs"
|
||||
rescue
|
||||
_-> IO.puts("!!! RUNNING IN LOCALHOST DEV MODE! !!!\nFEDERATION WON'T WORK UNTIL YOU CONFIGURE A dev.secret.exs")
|
||||
_ ->
|
||||
IO.puts(
|
||||
"!!! RUNNING IN LOCALHOST DEV MODE! !!!\nFEDERATION WON'T WORK UNTIL YOU CONFIGURE A dev.secret.exs"
|
||||
)
|
||||
end
|
||||
|
|
|
@ -9,8 +9,7 @@
|
|||
# Print only warnings and errors during test
|
||||
config :logger, level: :warn
|
||||
|
||||
config :pleroma, Pleroma.Upload,
|
||||
uploads: "test/uploads"
|
||||
config :pleroma, Pleroma.Upload, uploads: "test/uploads"
|
||||
|
||||
# Configure your database
|
||||
config :pleroma, Pleroma.Repo,
|
||||
|
@ -21,7 +20,6 @@
|
|||
hostname: System.get_env("DB_HOST") || "localhost",
|
||||
pool: Ecto.Adapters.SQL.Sandbox
|
||||
|
||||
|
||||
# Reduce hash rounds for testing
|
||||
config :comeonin, :pbkdf2_rounds, 1
|
||||
|
||||
|
|
|
@ -1 +1,5 @@
|
|||
Postgrex.Types.define(Pleroma.PostgresTypes, [] ++ Ecto.Adapters.Postgres.extensions(), json: Jason)
|
||||
Postgrex.Types.define(
|
||||
Pleroma.PostgresTypes,
|
||||
[] ++ Ecto.Adapters.Postgres.extensions(),
|
||||
json: Jason
|
||||
)
|
||||
|
|
|
@ -8,12 +8,16 @@ defmodule Mix.Tasks.FixApUsers do
|
|||
def run([]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
q = from u in User,
|
||||
where: fragment("? @> ?", u.info, ^%{"ap_enabled" => true}),
|
||||
where: u.local == false
|
||||
q =
|
||||
from(
|
||||
u in User,
|
||||
where: fragment("? @> ?", u.info, ^%{"ap_enabled" => true}),
|
||||
where: u.local == false
|
||||
)
|
||||
|
||||
users = Repo.all(q)
|
||||
|
||||
Enum.each(users, fn(user) ->
|
||||
Enum.each(users, fn user ->
|
||||
try do
|
||||
IO.puts("Fetching #{user.nickname}")
|
||||
Pleroma.Web.ActivityPub.Transmogrifier.upgrade_user_from_ap_id(user.ap_id, false)
|
||||
|
|
|
@ -5,27 +5,51 @@ defmodule Mix.Tasks.GenerateConfig do
|
|||
def run(_) do
|
||||
IO.puts("Answer a few questions to generate a new config\n")
|
||||
IO.puts("--- THIS WILL OVERWRITE YOUR config/generated_config.exs! ---\n")
|
||||
domain = IO.gets("What is your domain name? (e.g. pleroma.soykaf.com): ") |> String.trim
|
||||
name = IO.gets("What is the name of your instance? (e.g. Pleroma/Soykaf): ") |> String.trim
|
||||
email = IO.gets("What's your admin email address: ") |> String.trim
|
||||
mediaproxy = IO.gets("Do you want to activate the mediaproxy? (y/N): ")
|
||||
|> String.trim()
|
||||
|> String.downcase()
|
||||
|> String.starts_with?("y")
|
||||
proxy_url = if mediaproxy do
|
||||
IO.gets("What is the mediaproxy's URL? (e.g. https://cache.example.com): ") |> String.trim
|
||||
else
|
||||
"https://cache.example.com"
|
||||
end
|
||||
secret = :crypto.strong_rand_bytes(64) |> Base.encode64 |> binary_part(0, 64)
|
||||
dbpass = :crypto.strong_rand_bytes(64) |> Base.encode64 |> binary_part(0, 64)
|
||||
domain = IO.gets("What is your domain name? (e.g. pleroma.soykaf.com): ") |> String.trim()
|
||||
name = IO.gets("What is the name of your instance? (e.g. Pleroma/Soykaf): ") |> String.trim()
|
||||
email = IO.gets("What's your admin email address: ") |> String.trim()
|
||||
|
||||
resultSql = EEx.eval_file("lib/mix/tasks/sample_psql.eex", [dbpass: dbpass])
|
||||
result = EEx.eval_file("lib/mix/tasks/sample_config.eex", [domain: domain, email: email, name: name, secret: secret, mediaproxy: mediaproxy, proxy_url: proxy_url, dbpass: dbpass])
|
||||
mediaproxy =
|
||||
IO.gets("Do you want to activate the mediaproxy? (y/N): ")
|
||||
|> String.trim()
|
||||
|> String.downcase()
|
||||
|> String.starts_with?("y")
|
||||
|
||||
proxy_url =
|
||||
if mediaproxy do
|
||||
IO.gets("What is the mediaproxy's URL? (e.g. https://cache.example.com): ")
|
||||
|> String.trim()
|
||||
else
|
||||
"https://cache.example.com"
|
||||
end
|
||||
|
||||
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
||||
dbpass = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
||||
|
||||
resultSql = EEx.eval_file("lib/mix/tasks/sample_psql.eex", dbpass: dbpass)
|
||||
|
||||
result =
|
||||
EEx.eval_file(
|
||||
"lib/mix/tasks/sample_config.eex",
|
||||
domain: domain,
|
||||
email: email,
|
||||
name: name,
|
||||
secret: secret,
|
||||
mediaproxy: mediaproxy,
|
||||
proxy_url: proxy_url,
|
||||
dbpass: dbpass
|
||||
)
|
||||
|
||||
IO.puts(
|
||||
"\nWriting config to config/generated_config.exs.\n\nCheck it and configure your database, then copy it to either config/dev.secret.exs or config/prod.secret.exs"
|
||||
)
|
||||
|
||||
IO.puts("\nWriting config to config/generated_config.exs.\n\nCheck it and configure your database, then copy it to either config/dev.secret.exs or config/prod.secret.exs")
|
||||
File.write("config/generated_config.exs", result)
|
||||
IO.puts("\nWriting setup_db.psql, please run it as postgre superuser, i.e.: sudo su postgres -c 'psql -f config/setup_db.psql'")
|
||||
|
||||
IO.puts(
|
||||
"\nWriting setup_db.psql, please run it as postgre superuser, i.e.: sudo su postgres -c 'psql -f config/setup_db.psql'"
|
||||
)
|
||||
|
||||
File.write("config/setup_db.psql", resultSql)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,11 +9,20 @@ def run([nickname]) do
|
|||
|
||||
with %User{local: true} = user <- User.get_by_nickname(nickname),
|
||||
{:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
|
||||
IO.puts "Generated password reset token for #{user.nickname}"
|
||||
IO.puts "Url: #{Pleroma.Web.Router.Helpers.util_url(Pleroma.Web.Endpoint, :show_password_reset, token.token)}"
|
||||
IO.puts("Generated password reset token for #{user.nickname}")
|
||||
|
||||
IO.puts(
|
||||
"Url: #{
|
||||
Pleroma.Web.Router.Helpers.util_url(
|
||||
Pleroma.Web.Endpoint,
|
||||
:show_password_reset,
|
||||
token.token
|
||||
)
|
||||
}"
|
||||
)
|
||||
else
|
||||
_ ->
|
||||
IO.puts "No local user #{nickname}"
|
||||
IO.puts("No local user #{nickname}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,21 +7,24 @@ defmodule Mix.Tasks.SetModerator do
|
|||
def run([nickname | rest]) do
|
||||
ensure_started(Repo, [])
|
||||
|
||||
moderator = case rest do
|
||||
[moderator] -> moderator == "true"
|
||||
_ -> true
|
||||
end
|
||||
moderator =
|
||||
case rest do
|
||||
[moderator] -> moderator == "true"
|
||||
_ -> true
|
||||
end
|
||||
|
||||
with %User{local: true} = user <- User.get_by_nickname(nickname) do
|
||||
info = user.info
|
||||
|> Map.put("is_moderator", !!moderator)
|
||||
info =
|
||||
user.info
|
||||
|> Map.put("is_moderator", !!moderator)
|
||||
|
||||
cng = User.info_changeset(user, %{info: info})
|
||||
user = Repo.update!(cng)
|
||||
|
||||
IO.puts "Moderator status of #{nickname}: #{user.info["is_moderator"]}"
|
||||
IO.puts("Moderator status of #{nickname}: #{user.info["is_moderator"]}")
|
||||
else
|
||||
_ ->
|
||||
IO.puts "No local user #{nickname}"
|
||||
IO.puts("No local user #{nickname}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,15 +6,15 @@ defmodule Pleroma.PasswordResetToken do
|
|||
alias Pleroma.{User, PasswordResetToken, Repo}
|
||||
|
||||
schema "password_reset_tokens" do
|
||||
belongs_to :user, User
|
||||
field :token, :string
|
||||
field :used, :boolean, default: false
|
||||
belongs_to(:user, User)
|
||||
field(:token, :string)
|
||||
field(:used, :boolean, default: false)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def create_token(%User{} = user) do
|
||||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64
|
||||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64()
|
||||
|
||||
token = %PasswordResetToken{
|
||||
user_id: user.id,
|
||||
|
|
|
@ -4,33 +4,53 @@ defmodule Pleroma.Activity do
|
|||
import Ecto.Query
|
||||
|
||||
schema "activities" do
|
||||
field :data, :map
|
||||
field :local, :boolean, default: true
|
||||
field :actor, :string
|
||||
field :recipients, {:array, :string}
|
||||
has_many :notifications, Notification, on_delete: :delete_all
|
||||
field(:data, :map)
|
||||
field(:local, :boolean, default: true)
|
||||
field(:actor, :string)
|
||||
field(:recipients, {:array, :string})
|
||||
has_many(:notifications, Notification, on_delete: :delete_all)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def get_by_ap_id(ap_id) do
|
||||
Repo.one(from activity in Activity,
|
||||
where: fragment("(?)->>'id' = ?", activity.data, ^to_string(ap_id)))
|
||||
Repo.one(
|
||||
from(
|
||||
activity in Activity,
|
||||
where: fragment("(?)->>'id' = ?", activity.data, ^to_string(ap_id))
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
# TODO:
|
||||
# Go through these and fix them everywhere.
|
||||
# Wrong name, only returns create activities
|
||||
def all_by_object_ap_id_q(ap_id) do
|
||||
from activity in Activity,
|
||||
where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ?", activity.data, activity.data, ^to_string(ap_id)),
|
||||
from(
|
||||
activity in Activity,
|
||||
where:
|
||||
fragment(
|
||||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
|
||||
activity.data,
|
||||
activity.data,
|
||||
^to_string(ap_id)
|
||||
),
|
||||
where: fragment("(?)->>'type' = 'Create'", activity.data)
|
||||
)
|
||||
end
|
||||
|
||||
# Wrong name, returns all.
|
||||
def all_non_create_by_object_ap_id_q(ap_id) do
|
||||
from activity in Activity,
|
||||
where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ?", activity.data, activity.data, ^to_string(ap_id))
|
||||
from(
|
||||
activity in Activity,
|
||||
where:
|
||||
fragment(
|
||||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
|
||||
activity.data,
|
||||
activity.data,
|
||||
^to_string(ap_id)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
# Wrong name plz fix thx
|
||||
|
@ -39,13 +59,21 @@ def all_by_object_ap_id(ap_id) do
|
|||
end
|
||||
|
||||
def create_activity_by_object_id_query(ap_ids) do
|
||||
from activity in Activity,
|
||||
where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)", activity.data, activity.data, ^ap_ids),
|
||||
from(
|
||||
activity in Activity,
|
||||
where:
|
||||
fragment(
|
||||
"coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)",
|
||||
activity.data,
|
||||
activity.data,
|
||||
^ap_ids
|
||||
),
|
||||
where: fragment("(?)->>'type' = 'Create'", activity.data)
|
||||
)
|
||||
end
|
||||
|
||||
def get_create_activity_by_object_ap_id(ap_id) do
|
||||
create_activity_by_object_id_query([ap_id])
|
||||
|> Repo.one
|
||||
|> Repo.one()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,23 +7,34 @@ def start(_type, _args) do
|
|||
import Supervisor.Spec
|
||||
|
||||
# Define workers and child supervisors to be supervised
|
||||
children = [
|
||||
# Start the Ecto repository
|
||||
supervisor(Pleroma.Repo, []),
|
||||
# Start the endpoint when the application starts
|
||||
supervisor(Pleroma.Web.Endpoint, []),
|
||||
# Start your own worker by calling: Pleroma.Worker.start_link(arg1, arg2, arg3)
|
||||
# worker(Pleroma.Worker, [arg1, arg2, arg3]),
|
||||
worker(Cachex, [:user_cache, [
|
||||
default_ttl: 25000,
|
||||
ttl_interval: 1000,
|
||||
limit: 2500
|
||||
]]),
|
||||
worker(Pleroma.Web.Federator, []),
|
||||
worker(Pleroma.Stats, []),
|
||||
]
|
||||
++ if Mix.env == :test, do: [], else: [worker(Pleroma.Web.Streamer, [])]
|
||||
++ if !chat_enabled(), do: [], else: [worker(Pleroma.Web.ChatChannel.ChatChannelState, [])]
|
||||
children =
|
||||
[
|
||||
# Start the Ecto repository
|
||||
supervisor(Pleroma.Repo, []),
|
||||
# Start the endpoint when the application starts
|
||||
supervisor(Pleroma.Web.Endpoint, []),
|
||||
# Start your own worker by calling: Pleroma.Worker.start_link(arg1, arg2, arg3)
|
||||
# worker(Pleroma.Worker, [arg1, arg2, arg3]),
|
||||
worker(Cachex, [
|
||||
:user_cache,
|
||||
[
|
||||
default_ttl: 25000,
|
||||
ttl_interval: 1000,
|
||||
limit: 2500
|
||||
]
|
||||
]),
|
||||
worker(Pleroma.Web.Federator, []),
|
||||
worker(Pleroma.Stats, [])
|
||||
] ++
|
||||
if Mix.env() == :test,
|
||||
do: [],
|
||||
else:
|
||||
[worker(Pleroma.Web.Streamer, [])] ++
|
||||
if(
|
||||
!chat_enabled(),
|
||||
do: [],
|
||||
else: [worker(Pleroma.Web.ChatChannel.ChatChannelState, [])]
|
||||
)
|
||||
|
||||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
|
|
|
@ -5,19 +5,26 @@ defmodule Pleroma.Formatter do
|
|||
@tag_regex ~r/\#\w+/u
|
||||
def parse_tags(text, data \\ %{}) do
|
||||
Regex.scan(@tag_regex, text)
|
||||
|> Enum.map(fn (["#" <> tag = full_tag]) -> {full_tag, String.downcase(tag)} end)
|
||||
|> (fn map -> if data["sensitive"] in [true, "True", "true", "1"], do: [{"#nsfw", "nsfw"}] ++ map, else: map end).()
|
||||
|> Enum.map(fn ["#" <> tag = full_tag] -> {full_tag, String.downcase(tag)} end)
|
||||
|> (fn map ->
|
||||
if data["sensitive"] in [true, "True", "true", "1"],
|
||||
do: [{"#nsfw", "nsfw"}] ++ map,
|
||||
else: map
|
||||
end).()
|
||||
end
|
||||
|
||||
def parse_mentions(text) do
|
||||
# Modified from https://www.w3.org/TR/html5/forms.html#valid-e-mail-address
|
||||
regex = ~r/@[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@?[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/u
|
||||
regex =
|
||||
~r/@[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@?[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/u
|
||||
|
||||
Regex.scan(regex, text)
|
||||
|> List.flatten
|
||||
|> Enum.uniq
|
||||
|> Enum.map(fn ("@" <> match = full_match) -> {full_match, User.get_cached_by_nickname(match)} end)
|
||||
|> Enum.filter(fn ({_match, user}) -> user end)
|
||||
|> List.flatten()
|
||||
|> Enum.uniq()
|
||||
|> Enum.map(fn "@" <> match = full_match ->
|
||||
{full_match, User.get_cached_by_nickname(match)}
|
||||
end)
|
||||
|> Enum.filter(fn {_match, user} -> user end)
|
||||
end
|
||||
|
||||
@finmoji [
|
||||
|
@ -86,9 +93,9 @@ def parse_mentions(text) do
|
|||
"woollysocks"
|
||||
]
|
||||
|
||||
@finmoji_with_filenames Enum.map(@finmoji, fn (finmoji) ->
|
||||
{finmoji, "/finmoji/128px/#{finmoji}-128.png"}
|
||||
end)
|
||||
@finmoji_with_filenames Enum.map(@finmoji, fn finmoji ->
|
||||
{finmoji, "/finmoji/128px/#{finmoji}-128.png"}
|
||||
end)
|
||||
|
||||
@emoji_from_file (with {:ok, default} <- File.read("config/emoji.txt") do
|
||||
custom =
|
||||
|
@ -97,31 +104,40 @@ def parse_mentions(text) do
|
|||
else
|
||||
_e -> ""
|
||||
end
|
||||
|
||||
(default <> "\n" <> custom)
|
||||
|> String.trim()
|
||||
|> String.split(~r/\n+/)
|
||||
|> Enum.map(fn(line) ->
|
||||
|> Enum.map(fn line ->
|
||||
[name, file] = String.split(line, ~r/,\s*/)
|
||||
{name, file}
|
||||
end)
|
||||
end)
|
||||
else
|
||||
_ -> []
|
||||
end)
|
||||
end)
|
||||
|
||||
@emoji @finmoji_with_filenames ++ @emoji_from_file
|
||||
|
||||
def emojify(text, emoji \\ @emoji)
|
||||
def emojify(text, nil), do: text
|
||||
|
||||
def emojify(text, emoji) do
|
||||
Enum.reduce(emoji, text, fn ({emoji, file}, text) ->
|
||||
Enum.reduce(emoji, text, fn {emoji, file}, text ->
|
||||
emoji = HtmlSanitizeEx.strip_tags(emoji)
|
||||
file = HtmlSanitizeEx.strip_tags(file)
|
||||
String.replace(text, ":#{emoji}:", "<img height='32px' width='32px' alt='#{emoji}' title='#{emoji}' src='#{MediaProxy.url(file)}' />")
|
||||
|
||||
String.replace(
|
||||
text,
|
||||
":#{emoji}:",
|
||||
"<img height='32px' width='32px' alt='#{emoji}' title='#{emoji}' src='#{
|
||||
MediaProxy.url(file)
|
||||
}' />"
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
def get_emoji(text) do
|
||||
Enum.filter(@emoji, fn ({emoji, _}) -> String.contains?(text, ":#{emoji}:") end)
|
||||
Enum.filter(@emoji, fn {emoji, _} -> String.contains?(text, ":#{emoji}:") end)
|
||||
end
|
||||
|
||||
def get_custom_emoji() do
|
||||
|
@ -141,59 +157,71 @@ def html_escape(text) do
|
|||
|
||||
@doc "changes http:... links to html links"
|
||||
def add_links({subs, text}) do
|
||||
links = Regex.scan(@link_regex, text)
|
||||
|> Enum.map(fn ([url]) -> {Ecto.UUID.generate, url} end)
|
||||
links =
|
||||
Regex.scan(@link_regex, text)
|
||||
|> Enum.map(fn [url] -> {Ecto.UUID.generate(), url} end)
|
||||
|
||||
uuid_text = links
|
||||
|> Enum.reduce(text, fn({uuid, url}, acc) -> String.replace(acc, url, uuid) end)
|
||||
uuid_text =
|
||||
links
|
||||
|> Enum.reduce(text, fn {uuid, url}, acc -> String.replace(acc, url, uuid) end)
|
||||
|
||||
subs = subs ++ Enum.map(links, fn({uuid, url}) ->
|
||||
{uuid, "<a href='#{url}'>#{url}</a>"}
|
||||
end)
|
||||
subs =
|
||||
subs ++
|
||||
Enum.map(links, fn {uuid, url} ->
|
||||
{uuid, "<a href='#{url}'>#{url}</a>"}
|
||||
end)
|
||||
|
||||
{subs, uuid_text}
|
||||
end
|
||||
|
||||
@doc "Adds the links to mentioned users"
|
||||
def add_user_links({subs, text}, mentions) do
|
||||
mentions = mentions
|
||||
|> Enum.sort_by(fn ({name, _}) -> -String.length(name) end)
|
||||
|> Enum.map(fn({name, user}) -> {name, user, Ecto.UUID.generate} end)
|
||||
mentions =
|
||||
mentions
|
||||
|> Enum.sort_by(fn {name, _} -> -String.length(name) end)
|
||||
|> Enum.map(fn {name, user} -> {name, user, Ecto.UUID.generate()} end)
|
||||
|
||||
uuid_text = mentions
|
||||
|> Enum.reduce(text, fn ({match, _user, uuid}, text) ->
|
||||
String.replace(text, match, uuid)
|
||||
end)
|
||||
uuid_text =
|
||||
mentions
|
||||
|> Enum.reduce(text, fn {match, _user, uuid}, text ->
|
||||
String.replace(text, match, uuid)
|
||||
end)
|
||||
|
||||
subs = subs ++ Enum.map(mentions, fn ({match, %User{ap_id: ap_id}, uuid}) ->
|
||||
short_match = String.split(match, "@") |> tl() |> hd()
|
||||
{uuid, "<span><a href='#{ap_id}'>@<span>#{short_match}</span></a></span>"}
|
||||
end)
|
||||
subs =
|
||||
subs ++
|
||||
Enum.map(mentions, fn {match, %User{ap_id: ap_id}, uuid} ->
|
||||
short_match = String.split(match, "@") |> tl() |> hd()
|
||||
{uuid, "<span><a href='#{ap_id}'>@<span>#{short_match}</span></a></span>"}
|
||||
end)
|
||||
|
||||
{subs, uuid_text}
|
||||
end
|
||||
|
||||
@doc "Adds the hashtag links"
|
||||
def add_hashtag_links({subs, text}, tags) do
|
||||
tags = tags
|
||||
|> Enum.sort_by(fn ({name, _}) -> -String.length(name) end)
|
||||
|> Enum.map(fn({name, short}) -> {name, short, Ecto.UUID.generate} end)
|
||||
tags =
|
||||
tags
|
||||
|> Enum.sort_by(fn {name, _} -> -String.length(name) end)
|
||||
|> Enum.map(fn {name, short} -> {name, short, Ecto.UUID.generate()} end)
|
||||
|
||||
uuid_text = tags
|
||||
|> Enum.reduce(text, fn ({match, _short, uuid}, text) ->
|
||||
String.replace(text, match, uuid)
|
||||
end)
|
||||
uuid_text =
|
||||
tags
|
||||
|> Enum.reduce(text, fn {match, _short, uuid}, text ->
|
||||
String.replace(text, match, uuid)
|
||||
end)
|
||||
|
||||
subs = subs ++ Enum.map(tags, fn ({_, tag, uuid}) ->
|
||||
url = "<a href='#{Pleroma.Web.base_url}/tag/#{tag}' rel='tag'>##{tag}</a>"
|
||||
{uuid, url}
|
||||
end)
|
||||
subs =
|
||||
subs ++
|
||||
Enum.map(tags, fn {_, tag, uuid} ->
|
||||
url = "<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>##{tag}</a>"
|
||||
{uuid, url}
|
||||
end)
|
||||
|
||||
{subs, uuid_text}
|
||||
end
|
||||
|
||||
def finalize({subs, text}) do
|
||||
Enum.reduce(subs, text, fn({uuid, replacement}, result_text) ->
|
||||
Enum.reduce(subs, text, fn {uuid, replacement}, result_text ->
|
||||
String.replace(result_text, uuid, replacement)
|
||||
end)
|
||||
end
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
|
||||
defmodule Pleroma.HTTP do
|
||||
use HTTPoison.Base
|
||||
|
||||
def process_request_options(options) do
|
||||
config = Application.get_env(:pleroma, :http, [])
|
||||
proxy = Keyword.get(config, :proxy_url, nil)
|
||||
|
||||
case proxy do
|
||||
nil -> options
|
||||
_ -> options ++ [proxy: proxy]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -4,75 +4,89 @@ defmodule Pleroma.Notification do
|
|||
import Ecto.Query
|
||||
|
||||
schema "notifications" do
|
||||
field :seen, :boolean, default: false
|
||||
belongs_to :user, Pleroma.User
|
||||
belongs_to :activity, Pleroma.Activity
|
||||
field(:seen, :boolean, default: false)
|
||||
belongs_to(:user, Pleroma.User)
|
||||
belongs_to(:activity, Pleroma.Activity)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
# TODO: Make generic and unify (see activity_pub.ex)
|
||||
defp restrict_max(query, %{"max_id" => max_id}) do
|
||||
from activity in query, where: activity.id < ^max_id
|
||||
from(activity in query, where: activity.id < ^max_id)
|
||||
end
|
||||
|
||||
defp restrict_max(query, _), do: query
|
||||
|
||||
defp restrict_since(query, %{"since_id" => since_id}) do
|
||||
from activity in query, where: activity.id > ^since_id
|
||||
from(activity in query, where: activity.id > ^since_id)
|
||||
end
|
||||
|
||||
defp restrict_since(query, _), do: query
|
||||
|
||||
def for_user(user, opts \\ %{}) do
|
||||
query = from n in Notification,
|
||||
where: n.user_id == ^user.id,
|
||||
order_by: [desc: n.id],
|
||||
preload: [:activity],
|
||||
limit: 20
|
||||
query =
|
||||
from(
|
||||
n in Notification,
|
||||
where: n.user_id == ^user.id,
|
||||
order_by: [desc: n.id],
|
||||
preload: [:activity],
|
||||
limit: 20
|
||||
)
|
||||
|
||||
query = query
|
||||
|> restrict_since(opts)
|
||||
|> restrict_max(opts)
|
||||
query =
|
||||
query
|
||||
|> restrict_since(opts)
|
||||
|> restrict_max(opts)
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
def get(%{id: user_id} = _user, id) do
|
||||
query = from n in Notification,
|
||||
where: n.id == ^id,
|
||||
preload: [:activity]
|
||||
query =
|
||||
from(
|
||||
n in Notification,
|
||||
where: n.id == ^id,
|
||||
preload: [:activity]
|
||||
)
|
||||
|
||||
notification = Repo.one(query)
|
||||
|
||||
case notification do
|
||||
%{user_id: ^user_id} ->
|
||||
{:ok, notification}
|
||||
|
||||
_ ->
|
||||
{:error, "Cannot get notification"}
|
||||
end
|
||||
end
|
||||
|
||||
def clear(user) do
|
||||
query = from n in Notification,
|
||||
where: n.user_id == ^user.id
|
||||
query = from(n in Notification, where: n.user_id == ^user.id)
|
||||
|
||||
Repo.delete_all(query)
|
||||
end
|
||||
|
||||
def dismiss(%{id: user_id} = _user, id) do
|
||||
notification = Repo.get(Notification, id)
|
||||
|
||||
case notification do
|
||||
%{user_id: ^user_id} ->
|
||||
Repo.delete(notification)
|
||||
|
||||
_ ->
|
||||
{:error, "Cannot dismiss notification"}
|
||||
end
|
||||
end
|
||||
|
||||
def create_notifications(%Activity{id: _, data: %{"to" => _, "type" => type}} = activity) when type in ["Create", "Like", "Announce", "Follow"] do
|
||||
def create_notifications(%Activity{id: _, data: %{"to" => _, "type" => type}} = activity)
|
||||
when type in ["Create", "Like", "Announce", "Follow"] do
|
||||
users = User.get_notified_from_activity(activity)
|
||||
|
||||
notifications = Enum.map(users, fn (user) -> create_notification(activity, user) end)
|
||||
notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
|
||||
{:ok, notifications}
|
||||
end
|
||||
|
||||
def create_notifications(_), do: {:ok, []}
|
||||
|
||||
# TODO move to sql, too.
|
||||
|
@ -85,4 +99,3 @@ def create_notification(%Activity{} = activity, %User{} = user) do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -4,14 +4,14 @@ defmodule Pleroma.Object do
|
|||
import Ecto.{Query, Changeset}
|
||||
|
||||
schema "objects" do
|
||||
field :data, :map
|
||||
field(:data, :map)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def create(data) do
|
||||
Object.change(%Object{}, %{data: data})
|
||||
|> Repo.insert
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
def change(struct, params \\ %{}) do
|
||||
|
@ -22,24 +22,30 @@ def change(struct, params \\ %{}) do
|
|||
end
|
||||
|
||||
def get_by_ap_id(nil), do: nil
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
else
|
||||
key = "object:#{ap_id}"
|
||||
Cachex.get!(:user_cache, key, fallback: fn(_) ->
|
||||
object = get_by_ap_id(ap_id)
|
||||
if object do
|
||||
{:commit, object}
|
||||
else
|
||||
{:ignore, object}
|
||||
|
||||
Cachex.get!(
|
||||
:user_cache,
|
||||
key,
|
||||
fallback: fn _ ->
|
||||
object = get_by_ap_id(ap_id)
|
||||
|
||||
if object do
|
||||
{:commit, object}
|
||||
else
|
||||
{:ignore, object}
|
||||
end
|
||||
end
|
||||
end)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -14,8 +14,7 @@ def call(conn, opts) do
|
|||
{:ok, user} <- opts[:fetcher].(username),
|
||||
false <- !!user.info["deactivated"],
|
||||
saved_user_id <- get_session(conn, :user_id),
|
||||
{:ok, verified_user} <- verify(user, password, saved_user_id)
|
||||
do
|
||||
{:ok, verified_user} <- verify(user, password, saved_user_id) do
|
||||
conn
|
||||
|> assign(:user, verified_user)
|
||||
|> put_session(:user_id, verified_user.id)
|
||||
|
@ -30,7 +29,7 @@ defp verify(%{id: id} = user, _password, id) do
|
|||
end
|
||||
|
||||
defp verify(nil, _password, _user_id) do
|
||||
Pbkdf2.dummy_checkpw
|
||||
Pbkdf2.dummy_checkpw()
|
||||
:error
|
||||
end
|
||||
|
||||
|
@ -45,8 +44,7 @@ defp verify(user, password, _user_id) do
|
|||
defp decode_header(conn) do
|
||||
with ["Basic " <> header] <- get_req_header(conn, "authorization"),
|
||||
{:ok, userinfo} <- Base.decode64(header),
|
||||
[username, password] <- String.split(userinfo, ":", parts: 2)
|
||||
do
|
||||
[username, password] <- String.split(userinfo, ":", parts: 2) do
|
||||
{:ok, username, password}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,11 +9,14 @@ def init(options) do
|
|||
end
|
||||
|
||||
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
|
||||
|
||||
def call(conn, _) do
|
||||
token = case get_req_header(conn, "authorization") do
|
||||
["Bearer " <> header] -> header
|
||||
_ -> get_session(conn, :oauth_token)
|
||||
end
|
||||
token =
|
||||
case get_req_header(conn, "authorization") do
|
||||
["Bearer " <> header] -> header
|
||||
_ -> get_session(conn, :oauth_token)
|
||||
end
|
||||
|
||||
with token when not is_nil(token) <- token,
|
||||
%Token{user_id: user_id} <- Repo.get_by(Token, token: token),
|
||||
%User{} = user <- Repo.get(User, user_id),
|
||||
|
|
|
@ -18,22 +18,31 @@ def get_peers do
|
|||
|
||||
def schedule_update do
|
||||
spawn(fn ->
|
||||
Process.sleep(1000 * 60 * 60 * 1) # 1 hour
|
||||
# 1 hour
|
||||
Process.sleep(1000 * 60 * 60 * 1)
|
||||
schedule_update()
|
||||
end)
|
||||
|
||||
update_stats()
|
||||
end
|
||||
|
||||
def update_stats do
|
||||
peers = from(u in Pleroma.User,
|
||||
select: fragment("distinct ?->'host'", u.info),
|
||||
where: u.local != ^true)
|
||||
|> Repo.all()
|
||||
peers =
|
||||
from(
|
||||
u in Pleroma.User,
|
||||
select: fragment("distinct ?->'host'", u.info),
|
||||
where: u.local != ^true
|
||||
)
|
||||
|> Repo.all()
|
||||
|
||||
domain_count = Enum.count(peers)
|
||||
status_query = from(u in User.local_user_query,
|
||||
select: fragment("sum((?->>'note_count')::int)", u.info))
|
||||
|
||||
status_query =
|
||||
from(u in User.local_user_query(), select: fragment("sum((?->>'note_count')::int)", u.info))
|
||||
|
||||
status_count = Repo.one(status_query)
|
||||
user_count = Repo.aggregate(User.local_user_query, :count, :id)
|
||||
user_count = Repo.aggregate(User.local_user_query(), :count, :id)
|
||||
|
||||
Agent.update(__MODULE__, fn _ ->
|
||||
{peers, %{domain_count: domain_count, status_count: status_count, user_count: user_count}}
|
||||
end)
|
||||
|
|
|
@ -1,27 +1,31 @@
|
|||
defmodule Pleroma.Upload do
|
||||
alias Ecto.UUID
|
||||
alias Pleroma.Web
|
||||
|
||||
def store(%Plug.Upload{} = file) do
|
||||
uuid = UUID.generate
|
||||
uuid = UUID.generate()
|
||||
upload_folder = Path.join(upload_path(), uuid)
|
||||
File.mkdir_p!(upload_folder)
|
||||
result_file = Path.join(upload_folder, file.filename)
|
||||
File.cp!(file.path, result_file)
|
||||
|
||||
# fix content type on some image uploads
|
||||
content_type = if file.content_type in [nil, "application/octet-stream"] do
|
||||
get_content_type(file.path)
|
||||
else
|
||||
file.content_type
|
||||
end
|
||||
content_type =
|
||||
if file.content_type in [nil, "application/octet-stream"] do
|
||||
get_content_type(file.path)
|
||||
else
|
||||
file.content_type
|
||||
end
|
||||
|
||||
%{
|
||||
"type" => "Image",
|
||||
"url" => [%{
|
||||
"type" => "Link",
|
||||
"mediaType" => content_type,
|
||||
"href" => url_for(Path.join(uuid, :cow_uri.urlencode(file.filename)))
|
||||
}],
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => content_type,
|
||||
"href" => url_for(Path.join(uuid, :cow_uri.urlencode(file.filename)))
|
||||
}
|
||||
],
|
||||
"name" => file.filename,
|
||||
"uuid" => uuid
|
||||
}
|
||||
|
@ -30,7 +34,7 @@ def store(%Plug.Upload{} = file) do
|
|||
def store(%{"img" => "data:image/" <> image_data}) do
|
||||
parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data)
|
||||
data = Base.decode64!(parsed["data"])
|
||||
uuid = UUID.generate
|
||||
uuid = UUID.generate()
|
||||
upload_folder = Path.join(upload_path(), uuid)
|
||||
File.mkdir_p!(upload_folder)
|
||||
filename = Base.encode16(:crypto.hash(:sha256, data)) <> ".#{parsed["filetype"]}"
|
||||
|
@ -42,11 +46,13 @@ def store(%{"img" => "data:image/" <> image_data}) do
|
|||
|
||||
%{
|
||||
"type" => "Image",
|
||||
"url" => [%{
|
||||
"type" => "Link",
|
||||
"mediaType" => content_type,
|
||||
"href" => url_for(Path.join(uuid, :cow_uri.urlencode(filename)))
|
||||
}],
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => content_type,
|
||||
"href" => url_for(Path.join(uuid, :cow_uri.urlencode(filename)))
|
||||
}
|
||||
],
|
||||
"name" => filename,
|
||||
"uuid" => uuid
|
||||
}
|
||||
|
@ -62,28 +68,37 @@ defp url_for(file) do
|
|||
end
|
||||
|
||||
def get_content_type(file) do
|
||||
match = File.open(file, [:read], fn(f) ->
|
||||
case IO.binread(f, 8) do
|
||||
<<0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a>> ->
|
||||
"image/png"
|
||||
<<0x47, 0x49, 0x46, 0x38, _, 0x61, _, _>> ->
|
||||
"image/gif"
|
||||
<<0xff, 0xd8, 0xff, _, _, _, _, _>> ->
|
||||
"image/jpeg"
|
||||
<<0x1a, 0x45, 0xdf, 0xa3, _, _, _, _>> ->
|
||||
"video/webm"
|
||||
<<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70>> ->
|
||||
"video/mp4"
|
||||
<<0x49, 0x44, 0x33, _, _, _, _, _>> ->
|
||||
"audio/mpeg"
|
||||
<<0x4f, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00>> ->
|
||||
"audio/ogg"
|
||||
<<0x52, 0x49, 0x46, 0x46, _, _, _, _>> ->
|
||||
"audio/wav"
|
||||
_ ->
|
||||
"application/octet-stream"
|
||||
end
|
||||
end)
|
||||
match =
|
||||
File.open(file, [:read], fn f ->
|
||||
case IO.binread(f, 8) do
|
||||
<<0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A>> ->
|
||||
"image/png"
|
||||
|
||||
<<0x47, 0x49, 0x46, 0x38, _, 0x61, _, _>> ->
|
||||
"image/gif"
|
||||
|
||||
<<0xFF, 0xD8, 0xFF, _, _, _, _, _>> ->
|
||||
"image/jpeg"
|
||||
|
||||
<<0x1A, 0x45, 0xDF, 0xA3, _, _, _, _>> ->
|
||||
"video/webm"
|
||||
|
||||
<<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70>> ->
|
||||
"video/mp4"
|
||||
|
||||
<<0x49, 0x44, 0x33, _, _, _, _, _>> ->
|
||||
"audio/mpeg"
|
||||
|
||||
<<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00>> ->
|
||||
"audio/ogg"
|
||||
|
||||
<<0x52, 0x49, 0x46, 0x46, _, _, _, _>> ->
|
||||
"audio/wav"
|
||||
|
||||
_ ->
|
||||
"application/octet-stream"
|
||||
end
|
||||
end)
|
||||
|
||||
case match do
|
||||
{:ok, type} -> type
|
||||
|
|
|
@ -8,20 +8,20 @@ defmodule Pleroma.User do
|
|||
alias Pleroma.Web.ActivityPub.{Utils, ActivityPub}
|
||||
|
||||
schema "users" do
|
||||
field :bio, :string
|
||||
field :email, :string
|
||||
field :name, :string
|
||||
field :nickname, :string
|
||||
field :password_hash, :string
|
||||
field :password, :string, virtual: true
|
||||
field :password_confirmation, :string, virtual: true
|
||||
field :following, {:array, :string}, default: []
|
||||
field :ap_id, :string
|
||||
field :avatar, :map
|
||||
field :local, :boolean, default: true
|
||||
field :info, :map, default: %{}
|
||||
field :follower_address, :string
|
||||
has_many :notifications, Notification
|
||||
field(:bio, :string)
|
||||
field(:email, :string)
|
||||
field(:name, :string)
|
||||
field(:nickname, :string)
|
||||
field(:password_hash, :string)
|
||||
field(:password, :string, virtual: true)
|
||||
field(:password_confirmation, :string, virtual: true)
|
||||
field(:following, {:array, :string}, default: [])
|
||||
field(:ap_id, :string)
|
||||
field(:avatar, :map)
|
||||
field(:local, :boolean, default: true)
|
||||
field(:info, :map, default: %{})
|
||||
field(:follower_address, :string)
|
||||
has_many(:notifications, Notification)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
@ -41,7 +41,7 @@ def banner_url(user) do
|
|||
end
|
||||
|
||||
def ap_id(%User{nickname: nickname}) do
|
||||
"#{Web.base_url}/users/#{nickname}"
|
||||
"#{Web.base_url()}/users/#{nickname}"
|
||||
end
|
||||
|
||||
def ap_followers(%User{} = user) do
|
||||
|
@ -62,6 +62,7 @@ def info_changeset(struct, params \\ %{}) do
|
|||
|
||||
def user_info(%User{} = user) do
|
||||
oneself = if user.local, do: 1, else: 0
|
||||
|
||||
%{
|
||||
following_count: length(user.following) - oneself,
|
||||
note_count: user.info["note_count"] || 0,
|
||||
|
@ -71,21 +72,25 @@ def user_info(%User{} = user) do
|
|||
|
||||
@email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
|
||||
def remote_user_creation(params) do
|
||||
changes = %User{}
|
||||
|> cast(params, [:bio, :name, :ap_id, :nickname, :info, :avatar])
|
||||
|> validate_required([:name, :ap_id, :nickname])
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_format(:nickname, @email_regex)
|
||||
|> validate_length(:bio, max: 5000)
|
||||
|> validate_length(:name, max: 100)
|
||||
|> put_change(:local, false)
|
||||
changes =
|
||||
%User{}
|
||||
|> cast(params, [:bio, :name, :ap_id, :nickname, :info, :avatar])
|
||||
|> validate_required([:name, :ap_id, :nickname])
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_format(:nickname, @email_regex)
|
||||
|> validate_length(:bio, max: 5000)
|
||||
|> validate_length(:name, max: 100)
|
||||
|> put_change(:local, false)
|
||||
|
||||
if changes.valid? do
|
||||
case changes.changes[:info]["source_data"] do
|
||||
%{"followers" => followers} ->
|
||||
changes
|
||||
|> put_change(:follower_address, followers)
|
||||
|
||||
_ ->
|
||||
followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
|
||||
|
||||
changes
|
||||
|> put_change(:follower_address, followers)
|
||||
end
|
||||
|
@ -113,13 +118,15 @@ def upgrade_changeset(struct, params \\ %{}) do
|
|||
end
|
||||
|
||||
def password_update_changeset(struct, params) do
|
||||
changeset = struct
|
||||
|> cast(params, [:password, :password_confirmation])
|
||||
|> validate_required([:password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
changeset =
|
||||
struct
|
||||
|> cast(params, [:password, :password_confirmation])
|
||||
|> validate_required([:password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|
||||
if changeset.valid? do
|
||||
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
||||
|
||||
changeset
|
||||
|> put_change(:password_hash, hashed)
|
||||
else
|
||||
|
@ -132,21 +139,23 @@ def reset_password(user, data) do
|
|||
end
|
||||
|
||||
def register_changeset(struct, params \\ %{}) do
|
||||
changeset = struct
|
||||
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|
||||
|> validate_required([:email, :name, :nickname, :password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|> unique_constraint(:email)
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/)
|
||||
|> validate_format(:email, @email_regex)
|
||||
|> validate_length(:bio, max: 1000)
|
||||
|> validate_length(:name, min: 1, max: 100)
|
||||
changeset =
|
||||
struct
|
||||
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|
||||
|> validate_required([:email, :name, :nickname, :password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|> unique_constraint(:email)
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/)
|
||||
|> validate_format(:email, @email_regex)
|
||||
|> validate_length(:bio, max: 1000)
|
||||
|> validate_length(:name, min: 1, max: 100)
|
||||
|
||||
if changeset.valid? do
|
||||
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
||||
ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
|
||||
followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]})
|
||||
|
||||
changeset
|
||||
|> put_change(:password_hash, hashed)
|
||||
|> put_change(:ap_id, ap_id)
|
||||
|
@ -161,19 +170,20 @@ def follow(%User{} = follower, %User{info: info} = followed) do
|
|||
ap_followers = followed.follower_address
|
||||
|
||||
if following?(follower, followed) or info["deactivated"] do
|
||||
{:error,
|
||||
"Could not follow user: #{followed.nickname} is already on your list."}
|
||||
{:error, "Could not follow user: #{followed.nickname} is already on your list."}
|
||||
else
|
||||
if !followed.local && follower.local && !ap_enabled?(followed) do
|
||||
Websub.subscribe(follower, followed)
|
||||
end
|
||||
|
||||
following = [ap_followers | follower.following]
|
||||
|> Enum.uniq
|
||||
following =
|
||||
[ap_followers | follower.following]
|
||||
|> Enum.uniq()
|
||||
|
||||
follower = follower
|
||||
|> follow_changeset(%{following: following})
|
||||
|> update_and_set_cache
|
||||
follower =
|
||||
follower
|
||||
|> follow_changeset(%{following: following})
|
||||
|> update_and_set_cache
|
||||
|
||||
{:ok, _} = update_follower_count(followed)
|
||||
|
||||
|
@ -183,13 +193,16 @@ def follow(%User{} = follower, %User{info: info} = followed) do
|
|||
|
||||
def unfollow(%User{} = follower, %User{} = followed) do
|
||||
ap_followers = followed.follower_address
|
||||
if following?(follower, followed) and follower.ap_id != followed.ap_id do
|
||||
following = follower.following
|
||||
|> List.delete(ap_followers)
|
||||
|
||||
{ :ok, follower } = follower
|
||||
|> follow_changeset(%{following: following})
|
||||
|> update_and_set_cache
|
||||
if following?(follower, followed) and follower.ap_id != followed.ap_id do
|
||||
following =
|
||||
follower.following
|
||||
|> List.delete(ap_followers)
|
||||
|
||||
{:ok, follower} =
|
||||
follower
|
||||
|> follow_changeset(%{following: following})
|
||||
|> update_and_set_cache
|
||||
|
||||
{:ok, followed} = update_follower_count(followed)
|
||||
|
||||
|
@ -225,12 +238,12 @@ def invalidate_cache(user) do
|
|||
|
||||
def get_cached_by_ap_id(ap_id) do
|
||||
key = "ap_id:#{ap_id}"
|
||||
Cachex.get!(:user_cache, key, fallback: fn(_) -> get_by_ap_id(ap_id) end)
|
||||
Cachex.get!(:user_cache, key, fallback: fn _ -> get_by_ap_id(ap_id) end)
|
||||
end
|
||||
|
||||
def get_cached_by_nickname(nickname) do
|
||||
key = "nickname:#{nickname}"
|
||||
Cachex.get!(:user_cache, key, fallback: fn(_) -> get_or_fetch_by_nickname(nickname) end)
|
||||
Cachex.get!(:user_cache, key, fallback: fn _ -> get_or_fetch_by_nickname(nickname) end)
|
||||
end
|
||||
|
||||
def get_by_nickname(nickname) do
|
||||
|
@ -239,7 +252,7 @@ def get_by_nickname(nickname) do
|
|||
|
||||
def get_cached_user_info(user) do
|
||||
key = "user_info:#{user.id}"
|
||||
Cachex.get!(:user_cache, key, fallback: fn(_) -> user_info(user) end)
|
||||
Cachex.get!(:user_cache, key, fallback: fn _ -> user_info(user) end)
|
||||
end
|
||||
|
||||
def fetch_by_nickname(nickname) do
|
||||
|
@ -252,29 +265,37 @@ def fetch_by_nickname(nickname) do
|
|||
end
|
||||
|
||||
def get_or_fetch_by_nickname(nickname) do
|
||||
with %User{} = user <- get_by_nickname(nickname) do
|
||||
with %User{} = user <- get_by_nickname(nickname) do
|
||||
user
|
||||
else _e ->
|
||||
with [_nick, _domain] <- String.split(nickname, "@"),
|
||||
{:ok, user} <- fetch_by_nickname(nickname) do
|
||||
user
|
||||
else _e -> nil
|
||||
end
|
||||
else
|
||||
_e ->
|
||||
with [_nick, _domain] <- String.split(nickname, "@"),
|
||||
{:ok, user} <- fetch_by_nickname(nickname) do
|
||||
user
|
||||
else
|
||||
_e -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_followers(%User{id: id, follower_address: follower_address}) do
|
||||
q = from u in User,
|
||||
where: fragment("? <@ ?", ^[follower_address], u.following),
|
||||
where: u.id != ^id
|
||||
q =
|
||||
from(
|
||||
u in User,
|
||||
where: fragment("? <@ ?", ^[follower_address], u.following),
|
||||
where: u.id != ^id
|
||||
)
|
||||
|
||||
{:ok, Repo.all(q)}
|
||||
end
|
||||
|
||||
def get_friends(%User{id: id, following: following}) do
|
||||
q = from u in User,
|
||||
where: u.follower_address in ^following,
|
||||
where: u.id != ^id
|
||||
q =
|
||||
from(
|
||||
u in User,
|
||||
where: u.follower_address in ^following,
|
||||
where: u.id != ^id
|
||||
)
|
||||
|
||||
{:ok, Repo.all(q)}
|
||||
end
|
||||
|
@ -289,9 +310,12 @@ def increase_note_count(%User{} = user) do
|
|||
end
|
||||
|
||||
def update_note_count(%User{} = user) do
|
||||
note_count_query = from a in Object,
|
||||
where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
|
||||
select: count(a.id)
|
||||
note_count_query =
|
||||
from(
|
||||
a in Object,
|
||||
where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
|
||||
select: count(a.id)
|
||||
)
|
||||
|
||||
note_count = Repo.one(note_count_query)
|
||||
|
||||
|
@ -303,10 +327,13 @@ def update_note_count(%User{} = user) do
|
|||
end
|
||||
|
||||
def update_follower_count(%User{} = user) do
|
||||
follower_count_query = from u in User,
|
||||
where: ^user.follower_address in u.following,
|
||||
where: u.id != ^user.id,
|
||||
select: count(u.id)
|
||||
follower_count_query =
|
||||
from(
|
||||
u in User,
|
||||
where: ^user.follower_address in u.following,
|
||||
where: u.id != ^user.id,
|
||||
select: count(u.id)
|
||||
)
|
||||
|
||||
follower_count = Repo.one(follower_count_query)
|
||||
|
||||
|
@ -318,20 +345,25 @@ def update_follower_count(%User{} = user) do
|
|||
end
|
||||
|
||||
def get_notified_from_activity(%Activity{recipients: to}) do
|
||||
query = from u in User,
|
||||
where: u.ap_id in ^to,
|
||||
where: u.local == true
|
||||
query =
|
||||
from(
|
||||
u in User,
|
||||
where: u.ap_id in ^to,
|
||||
where: u.local == true
|
||||
)
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
def get_recipients_from_activity(%Activity{recipients: to}) do
|
||||
query = from u in User,
|
||||
where: u.ap_id in ^to,
|
||||
or_where: fragment("? && ?", u.following, ^to)
|
||||
query =
|
||||
from(
|
||||
u in User,
|
||||
where: u.ap_id in ^to,
|
||||
or_where: fragment("? && ?", u.following, ^to)
|
||||
)
|
||||
|
||||
query = from u in query,
|
||||
where: u.local == true
|
||||
query = from(u in query, where: u.local == true)
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
@ -340,9 +372,20 @@ def search(query, resolve) do
|
|||
if resolve do
|
||||
User.get_or_fetch_by_nickname(query)
|
||||
end
|
||||
q = from u in User,
|
||||
where: fragment("(to_tsvector('english', ?) || to_tsvector('english', ?)) @@ plainto_tsquery('english', ?)", u.nickname, u.name, ^query),
|
||||
limit: 20
|
||||
|
||||
q =
|
||||
from(
|
||||
u in User,
|
||||
where:
|
||||
fragment(
|
||||
"(to_tsvector('english', ?) || to_tsvector('english', ?)) @@ plainto_tsquery('english', ?)",
|
||||
u.nickname,
|
||||
u.name,
|
||||
^query
|
||||
),
|
||||
limit: 20
|
||||
)
|
||||
|
||||
Repo.all(q)
|
||||
end
|
||||
|
||||
|
@ -370,36 +413,40 @@ def blocks?(user, %{ap_id: ap_id}) do
|
|||
end
|
||||
|
||||
def local_user_query() do
|
||||
from u in User,
|
||||
where: u.local == true
|
||||
from(u in User, where: u.local == true)
|
||||
end
|
||||
|
||||
def deactivate (%User{} = user) do
|
||||
def deactivate(%User{} = user) do
|
||||
new_info = Map.put(user.info, "deactivated", true)
|
||||
cs = User.info_changeset(user, %{info: new_info})
|
||||
update_and_set_cache(cs)
|
||||
end
|
||||
|
||||
def delete (%User{} = user) do
|
||||
def delete(%User{} = user) do
|
||||
{:ok, user} = User.deactivate(user)
|
||||
|
||||
# Remove all relationships
|
||||
{:ok, followers } = User.get_followers(user)
|
||||
{:ok, followers} = User.get_followers(user)
|
||||
|
||||
followers
|
||||
|> Enum.each(fn (follower) -> User.unfollow(follower, user) end)
|
||||
|> Enum.each(fn follower -> User.unfollow(follower, user) end)
|
||||
|
||||
{:ok, friends} = User.get_friends(user)
|
||||
friends
|
||||
|> Enum.each(fn (followed) -> User.unfollow(user, followed) end)
|
||||
|
||||
query = from a in Activity,
|
||||
where: a.actor == ^user.ap_id
|
||||
friends
|
||||
|> Enum.each(fn followed -> User.unfollow(user, followed) end)
|
||||
|
||||
query = from(a in Activity, where: a.actor == ^user.ap_id)
|
||||
|
||||
Repo.all(query)
|
||||
|> Enum.each(fn (activity) ->
|
||||
|> Enum.each(fn activity ->
|
||||
case activity.data["type"] do
|
||||
"Create" -> ActivityPub.delete(Object.get_by_ap_id(activity.data["object"]["id"]))
|
||||
_ -> "Doing nothing" # TODO: Do something with likes, follows, repeats.
|
||||
"Create" ->
|
||||
ActivityPub.delete(Object.get_by_ap_id(activity.data["object"]["id"]))
|
||||
|
||||
# TODO: Do something with likes, follows, repeats.
|
||||
_ ->
|
||||
"Doing nothing"
|
||||
end
|
||||
end)
|
||||
|
||||
|
@ -413,7 +460,9 @@ def get_or_fetch_by_ap_id(ap_id) do
|
|||
ap_try = ActivityPub.make_user_from_ap_id(ap_id)
|
||||
|
||||
case ap_try do
|
||||
{:ok, user} -> user
|
||||
{:ok, user} ->
|
||||
user
|
||||
|
||||
_ ->
|
||||
case OStatus.make_user(ap_id) do
|
||||
{:ok, user} -> user
|
||||
|
@ -424,12 +473,15 @@ def get_or_fetch_by_ap_id(ap_id) do
|
|||
end
|
||||
|
||||
# AP style
|
||||
def public_key_from_info(%{"source_data" => %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
|
||||
key = :public_key.pem_decode(public_key_pem)
|
||||
|> hd()
|
||||
|> :public_key.pem_entry_decode()
|
||||
def public_key_from_info(%{
|
||||
"source_data" => %{"publicKey" => %{"publicKeyPem" => public_key_pem}}
|
||||
}) do
|
||||
key =
|
||||
:public_key.pem_decode(public_key_pem)
|
||||
|> hd()
|
||||
|> :public_key.pem_entry_decode()
|
||||
|
||||
{:ok, key}
|
||||
{:ok, key}
|
||||
end
|
||||
|
||||
# OStatus Magic Key
|
||||
|
@ -450,8 +502,10 @@ defp blank?(""), do: nil
|
|||
defp blank?(n), do: n
|
||||
|
||||
def insert_or_update_user(data) do
|
||||
data = data
|
||||
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|
||||
data =
|
||||
data
|
||||
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|
||||
|
||||
cs = User.remote_user_creation(data)
|
||||
Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
|
||||
end
|
||||
|
|
|
@ -18,7 +18,14 @@ def insert(map, local \\ true) when is_map(map) do
|
|||
with nil <- Activity.get_by_ap_id(map["id"]),
|
||||
map <- lazy_put_activity_defaults(map),
|
||||
:ok <- insert_full_object(map) do
|
||||
{:ok, activity} = Repo.insert(%Activity{data: map, local: local, actor: map["actor"], recipients: get_recipients(map)})
|
||||
{:ok, activity} =
|
||||
Repo.insert(%Activity{
|
||||
data: map,
|
||||
local: local,
|
||||
actor: map["actor"],
|
||||
recipients: get_recipients(map)
|
||||
})
|
||||
|
||||
Notification.create_notifications(activity)
|
||||
stream_out(activity)
|
||||
{:ok, activity}
|
||||
|
@ -31,8 +38,10 @@ def insert(map, local \\ true) when is_map(map) do
|
|||
def stream_out(activity) do
|
||||
if activity.data["type"] in ["Create", "Announce"] do
|
||||
Pleroma.Web.Streamer.stream("user", activity)
|
||||
|
||||
if Enum.member?(activity.data["to"], "https://www.w3.org/ns/activitystreams#Public") do
|
||||
Pleroma.Web.Streamer.stream("public", activity)
|
||||
|
||||
if activity.local do
|
||||
Pleroma.Web.Streamer.stream("public:local", activity)
|
||||
end
|
||||
|
@ -42,10 +51,15 @@ def stream_out(activity) do
|
|||
|
||||
def create(%{to: to, actor: actor, context: context, object: object} = params) do
|
||||
additional = params[:additional] || %{}
|
||||
local = !(params[:local] == false) # only accept false as false value
|
||||
# only accept false as false value
|
||||
local = !(params[:local] == false)
|
||||
published = params[:published]
|
||||
|
||||
with create_data <- make_create_data(%{to: to, actor: actor, published: published, context: context, object: object}, additional),
|
||||
with create_data <-
|
||||
make_create_data(
|
||||
%{to: to, actor: actor, published: published, context: context, object: object},
|
||||
additional
|
||||
),
|
||||
{:ok, activity} <- insert(create_data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
|
@ -53,7 +67,8 @@ def create(%{to: to, actor: actor, context: context, object: object} = params) d
|
|||
end
|
||||
|
||||
def accept(%{to: to, actor: actor, object: object} = params) do
|
||||
local = !(params[:local] == false) # only accept false as false value
|
||||
# only accept false as false value
|
||||
local = !(params[:local] == false)
|
||||
|
||||
with data <- %{"to" => to, "type" => "Accept", "actor" => actor, "object" => object},
|
||||
{:ok, activity} <- insert(data, local),
|
||||
|
@ -63,9 +78,16 @@ def accept(%{to: to, actor: actor, object: object} = params) do
|
|||
end
|
||||
|
||||
def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
|
||||
local = !(params[:local] == false) # only accept false as false value
|
||||
# only accept false as false value
|
||||
local = !(params[:local] == false)
|
||||
|
||||
with data <- %{"to" => to, "cc" => cc, "type" => "Update", "actor" => actor, "object" => object},
|
||||
with data <- %{
|
||||
"to" => to,
|
||||
"cc" => cc,
|
||||
"type" => "Update",
|
||||
"actor" => actor,
|
||||
"object" => object
|
||||
},
|
||||
{:ok, activity} <- insert(data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
|
@ -73,7 +95,12 @@ def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
|
|||
end
|
||||
|
||||
# TODO: This is weird, maybe we shouldn't check here if we can make the activity.
|
||||
def like(%User{ap_id: ap_id} = user, %Object{data: %{"id" => _}} = object, activity_id \\ nil, local \\ true) do
|
||||
def like(
|
||||
%User{ap_id: ap_id} = user,
|
||||
%Object{data: %{"id" => _}} = object,
|
||||
activity_id \\ nil,
|
||||
local \\ true
|
||||
) do
|
||||
with nil <- get_existing_like(ap_id, object),
|
||||
like_data <- make_like_data(user, object, activity_id),
|
||||
{:ok, activity} <- insert(like_data, local),
|
||||
|
@ -91,11 +118,17 @@ def unlike(%User{} = actor, %Object{} = object) do
|
|||
{:ok, _activity} <- Repo.delete(activity),
|
||||
{:ok, object} <- remove_like_from_object(activity, object) do
|
||||
{:ok, object}
|
||||
else _e -> {:ok, object}
|
||||
else
|
||||
_e -> {:ok, object}
|
||||
end
|
||||
end
|
||||
|
||||
def announce(%User{ap_id: _} = user, %Object{data: %{"id" => _}} = object, activity_id \\ nil, local \\ true) do
|
||||
def announce(
|
||||
%User{ap_id: _} = user,
|
||||
%Object{data: %{"id" => _}} = object,
|
||||
activity_id \\ nil,
|
||||
local \\ true
|
||||
) do
|
||||
with true <- is_public?(object),
|
||||
announce_data <- make_announce_data(user, object, activity_id),
|
||||
{:ok, activity} <- insert(announce_data, local),
|
||||
|
@ -119,19 +152,22 @@ def unfollow(follower, followed, local \\ true) do
|
|||
with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
|
||||
unfollow_data <- make_unfollow_data(follower, followed, follow_activity),
|
||||
{:ok, activity} <- insert(unfollow_data, local),
|
||||
:ok, maybe_federate(activity) do
|
||||
:ok,
|
||||
maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
|
||||
user = User.get_cached_by_ap_id(actor)
|
||||
|
||||
data = %{
|
||||
"type" => "Delete",
|
||||
"actor" => actor,
|
||||
"object" => id,
|
||||
"to" => [user.follower_address, "https://www.w3.org/ns/activitystreams#Public"]
|
||||
}
|
||||
|
||||
with Repo.delete(object),
|
||||
Repo.delete_all(Activity.all_non_create_by_object_ap_id_q(id)),
|
||||
{:ok, activity} <- insert(data, local),
|
||||
|
@ -142,112 +178,147 @@ def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ tru
|
|||
|
||||
def fetch_activities_for_context(context, opts \\ %{}) do
|
||||
public = ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
recipients = if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
|
||||
|
||||
query = from activity in Activity
|
||||
query = query
|
||||
recipients =
|
||||
if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
|
||||
|
||||
query = from(activity in Activity)
|
||||
|
||||
query =
|
||||
query
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_recipients(recipients, opts["user"])
|
||||
|
||||
query = from activity in query,
|
||||
where: fragment("?->>'type' = ? and ?->>'context' = ?", activity.data, "Create", activity.data, ^context),
|
||||
order_by: [desc: :id]
|
||||
query =
|
||||
from(
|
||||
activity in query,
|
||||
where:
|
||||
fragment(
|
||||
"?->>'type' = ? and ?->>'context' = ?",
|
||||
activity.data,
|
||||
"Create",
|
||||
activity.data,
|
||||
^context
|
||||
),
|
||||
order_by: [desc: :id]
|
||||
)
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
# TODO: Make this work properly with unlisted.
|
||||
def fetch_public_activities(opts \\ %{}) do
|
||||
q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts)
|
||||
|
||||
q
|
||||
|> Repo.all
|
||||
|> Enum.reverse
|
||||
|> Repo.all()
|
||||
|> Enum.reverse()
|
||||
end
|
||||
|
||||
defp restrict_since(query, %{"since_id" => since_id}) do
|
||||
from activity in query, where: activity.id > ^since_id
|
||||
from(activity in query, where: activity.id > ^since_id)
|
||||
end
|
||||
|
||||
defp restrict_since(query, _), do: query
|
||||
|
||||
defp restrict_tag(query, %{"tag" => tag}) do
|
||||
from activity in query,
|
||||
from(
|
||||
activity in query,
|
||||
where: fragment("? <@ (? #> '{\"object\",\"tag\"}')", ^tag, activity.data)
|
||||
)
|
||||
end
|
||||
|
||||
defp restrict_tag(query, _), do: query
|
||||
|
||||
defp restrict_recipients(query, [], user), do: query
|
||||
|
||||
defp restrict_recipients(query, recipients, nil) do
|
||||
from activity in query,
|
||||
where: fragment("? && ?", ^recipients, activity.recipients)
|
||||
from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients))
|
||||
end
|
||||
|
||||
defp restrict_recipients(query, recipients, user) do
|
||||
from activity in query,
|
||||
from(
|
||||
activity in query,
|
||||
where: fragment("? && ?", ^recipients, activity.recipients),
|
||||
or_where: activity.actor == ^user.ap_id
|
||||
)
|
||||
end
|
||||
|
||||
defp restrict_limit(query, %{"limit" => limit}) do
|
||||
from activity in query,
|
||||
limit: ^limit
|
||||
from(activity in query, limit: ^limit)
|
||||
end
|
||||
|
||||
defp restrict_limit(query, _), do: query
|
||||
|
||||
defp restrict_local(query, %{"local_only" => true}) do
|
||||
from activity in query, where: activity.local == true
|
||||
from(activity in query, where: activity.local == true)
|
||||
end
|
||||
|
||||
defp restrict_local(query, _), do: query
|
||||
|
||||
defp restrict_max(query, %{"max_id" => max_id}) do
|
||||
from activity in query, where: activity.id < ^max_id
|
||||
from(activity in query, where: activity.id < ^max_id)
|
||||
end
|
||||
|
||||
defp restrict_max(query, _), do: query
|
||||
|
||||
defp restrict_actor(query, %{"actor_id" => actor_id}) do
|
||||
from activity in query,
|
||||
where: activity.actor == ^actor_id
|
||||
from(activity in query, where: activity.actor == ^actor_id)
|
||||
end
|
||||
|
||||
defp restrict_actor(query, _), do: query
|
||||
|
||||
defp restrict_type(query, %{"type" => type}) when is_binary(type) do
|
||||
restrict_type(query, %{"type" => [type]})
|
||||
end
|
||||
|
||||
defp restrict_type(query, %{"type" => type}) do
|
||||
from activity in query,
|
||||
where: fragment("?->>'type' = ANY(?)", activity.data, ^type)
|
||||
from(activity in query, where: fragment("?->>'type' = ANY(?)", activity.data, ^type))
|
||||
end
|
||||
|
||||
defp restrict_type(query, _), do: query
|
||||
|
||||
defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
|
||||
from activity in query,
|
||||
from(
|
||||
activity in query,
|
||||
where: fragment("? <@ (? #> '{\"object\",\"likes\"}')", ^ap_id, activity.data)
|
||||
)
|
||||
end
|
||||
|
||||
defp restrict_favorited_by(query, _), do: query
|
||||
|
||||
defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
|
||||
from activity in query,
|
||||
from(
|
||||
activity in query,
|
||||
where: fragment("not (? #> '{\"object\",\"attachment\"}' = ?)", activity.data, ^[])
|
||||
)
|
||||
end
|
||||
|
||||
defp restrict_media(query, _), do: query
|
||||
|
||||
# Only search through last 100_000 activities by default
|
||||
defp restrict_recent(query, %{"whole_db" => true}), do: query
|
||||
|
||||
defp restrict_recent(query, _) do
|
||||
since = (Repo.aggregate(Activity, :max, :id) || 0) - 100_000
|
||||
|
||||
from activity in query,
|
||||
where: activity.id > ^since
|
||||
from(activity in query, where: activity.id > ^since)
|
||||
end
|
||||
|
||||
defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
|
||||
blocks = info["blocks"] || []
|
||||
from activity in query,
|
||||
where: fragment("not (? = ANY(?))", activity.actor, ^blocks)
|
||||
from(activity in query, where: fragment("not (? = ANY(?))", activity.actor, ^blocks))
|
||||
end
|
||||
|
||||
defp restrict_blocked(query, _), do: query
|
||||
|
||||
def fetch_activities_query(recipients, opts \\ %{}) do
|
||||
base_query = from activity in Activity,
|
||||
limit: 20,
|
||||
order_by: [fragment("? desc nulls last", activity.id)]
|
||||
base_query =
|
||||
from(
|
||||
activity in Activity,
|
||||
limit: 20,
|
||||
order_by: [fragment("? desc nulls last", activity.id)]
|
||||
)
|
||||
|
||||
base_query
|
||||
|> restrict_recipients(recipients, opts["user"])
|
||||
|
@ -266,8 +337,8 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
|||
|
||||
def fetch_activities(recipients, opts \\ %{}) do
|
||||
fetch_activities_query(recipients, opts)
|
||||
|> Repo.all
|
||||
|> Enum.reverse
|
||||
|> Repo.all()
|
||||
|> Enum.reverse()
|
||||
end
|
||||
|
||||
def upload(file) do
|
||||
|
@ -276,15 +347,19 @@ def upload(file) do
|
|||
end
|
||||
|
||||
def user_data_from_user_object(data) do
|
||||
avatar = data["icon"]["url"] && %{
|
||||
"type" => "Image",
|
||||
"url" => [%{"href" => data["icon"]["url"]}]
|
||||
}
|
||||
avatar =
|
||||
data["icon"]["url"] &&
|
||||
%{
|
||||
"type" => "Image",
|
||||
"url" => [%{"href" => data["icon"]["url"]}]
|
||||
}
|
||||
|
||||
banner = data["image"]["url"] && %{
|
||||
"type" => "Image",
|
||||
"url" => [%{"href" => data["image"]["url"]}]
|
||||
}
|
||||
banner =
|
||||
data["image"]["url"] &&
|
||||
%{
|
||||
"type" => "Image",
|
||||
"url" => [%{"href" => data["image"]["url"]}]
|
||||
}
|
||||
|
||||
user_data = %{
|
||||
ap_id: data["id"],
|
||||
|
@ -304,8 +379,9 @@ def user_data_from_user_object(data) do
|
|||
end
|
||||
|
||||
def fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||
with {:ok, %{status_code: 200, body: body}} <- @httpoison.get(ap_id, ["Accept": "application/activity+json"]),
|
||||
{:ok, data} <- Jason.decode(body) do
|
||||
with {:ok, %{status_code: 200, body: body}} <-
|
||||
@httpoison.get(ap_id, Accept: "application/activity+json"),
|
||||
{:ok, data} <- Jason.decode(body) do
|
||||
user_data_from_user_object(data)
|
||||
else
|
||||
e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
|
||||
|
@ -333,32 +409,48 @@ def make_user_from_nickname(nickname) do
|
|||
end
|
||||
|
||||
def publish(actor, activity) do
|
||||
followers = if actor.follower_address in activity.recipients do
|
||||
{:ok, followers} = User.get_followers(actor)
|
||||
followers |> Enum.filter(&(!&1.local))
|
||||
else
|
||||
[]
|
||||
end
|
||||
followers =
|
||||
if actor.follower_address in activity.recipients do
|
||||
{:ok, followers} = User.get_followers(actor)
|
||||
followers |> Enum.filter(&(!&1.local))
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
remote_inboxes = (Pleroma.Web.Salmon.remote_users(activity) ++ followers)
|
||||
|> Enum.filter(fn (user) -> User.ap_enabled?(user) end)
|
||||
|> Enum.map(fn (%{info: %{"source_data" => data}}) ->
|
||||
(data["endpoints"] && data["endpoints"]["sharedInbox"]) || data["inbox"]
|
||||
end)
|
||||
|> Enum.uniq
|
||||
remote_inboxes =
|
||||
(Pleroma.Web.Salmon.remote_users(activity) ++ followers)
|
||||
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|
||||
|> Enum.map(fn %{info: %{"source_data" => data}} ->
|
||||
(data["endpoints"] && data["endpoints"]["sharedInbox"]) || data["inbox"]
|
||||
end)
|
||||
|> Enum.uniq()
|
||||
|
||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
json = Jason.encode!(data)
|
||||
Enum.each remote_inboxes, fn(inbox) ->
|
||||
Federator.enqueue(:publish_single_ap, %{inbox: inbox, json: json, actor: actor, id: activity.data["id"]})
|
||||
end
|
||||
|
||||
Enum.each(remote_inboxes, fn inbox ->
|
||||
Federator.enqueue(:publish_single_ap, %{
|
||||
inbox: inbox,
|
||||
json: json,
|
||||
actor: actor,
|
||||
id: activity.data["id"]
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
|
||||
Logger.info("Federating #{id} to #{inbox}")
|
||||
host = URI.parse(inbox).host
|
||||
signature = Pleroma.Web.HTTPSignatures.sign(actor, %{host: host, "content-length": byte_size(json)})
|
||||
@httpoison.post(inbox, json, [{"Content-Type", "application/activity+json"}, {"signature", signature}], hackney: [pool: :default])
|
||||
|
||||
signature =
|
||||
Pleroma.Web.HTTPSignatures.sign(actor, %{host: host, "content-length": byte_size(json)})
|
||||
|
||||
@httpoison.post(
|
||||
inbox,
|
||||
json,
|
||||
[{"Content-Type", "application/activity+json"}, {"signature", signature}],
|
||||
hackney: [pool: :default]
|
||||
)
|
||||
end
|
||||
|
||||
# TODO:
|
||||
|
@ -368,17 +460,34 @@ def fetch_object_from_id(id) do
|
|||
{:ok, object}
|
||||
else
|
||||
Logger.info("Fetching #{id} via AP")
|
||||
|
||||
with true <- String.starts_with?(id, "http"),
|
||||
{:ok, %{body: body, status_code: code}} when code in 200..299 <- @httpoison.get(id, [Accept: "application/activity+json"], follow_redirect: true, timeout: 10000, recv_timeout: 20000),
|
||||
{:ok, %{body: body, status_code: code}} when code in 200..299 <-
|
||||
@httpoison.get(
|
||||
id,
|
||||
[Accept: "application/activity+json"],
|
||||
follow_redirect: true,
|
||||
timeout: 10000,
|
||||
recv_timeout: 20000
|
||||
),
|
||||
{:ok, data} <- Jason.decode(body),
|
||||
nil <- Object.get_by_ap_id(data["id"]),
|
||||
params <- %{"type" => "Create", "to" => data["to"], "cc" => data["cc"], "actor" => data["attributedTo"], "object" => data},
|
||||
params <- %{
|
||||
"type" => "Create",
|
||||
"to" => data["to"],
|
||||
"cc" => data["cc"],
|
||||
"actor" => data["attributedTo"],
|
||||
"object" => data
|
||||
},
|
||||
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
|
||||
{:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
|
||||
else
|
||||
object = %Object{} -> {:ok, object}
|
||||
object = %Object{} ->
|
||||
{:ok, object}
|
||||
|
||||
e ->
|
||||
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
|
||||
|
||||
case OStatus.fetch_activity_from_url(id) do
|
||||
{:ok, [activity | _]} -> {:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
|
||||
e -> e
|
||||
|
@ -388,15 +497,17 @@ def fetch_object_from_id(id) do
|
|||
end
|
||||
|
||||
def is_public?(activity) do
|
||||
"https://www.w3.org/ns/activitystreams#Public" in (activity.data["to"] ++ (activity.data["cc"] || []))
|
||||
"https://www.w3.org/ns/activitystreams#Public" in (activity.data["to"] ++
|
||||
(activity.data["cc"] || []))
|
||||
end
|
||||
|
||||
def visible_for_user?(activity, nil) do
|
||||
is_public?(activity)
|
||||
end
|
||||
|
||||
def visible_for_user?(activity, user) do
|
||||
x = [user.ap_id | user.following]
|
||||
y = (activity.data["to"] ++ (activity.data["cc"] || []))
|
||||
y = activity.data["to"] ++ (activity.data["cc"] || [])
|
||||
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
|
||||
require Logger
|
||||
|
||||
action_fallback :errors
|
||||
action_fallback(:errors)
|
||||
|
||||
def user(conn, %{"nickname" => nickname}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||
|
@ -31,6 +31,7 @@ def following(conn, %{"nickname" => nickname, "page" => page}) do
|
|||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
|
||||
{page, _} = Integer.parse(page)
|
||||
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|> json(UserView.render("following.json", %{user: user, page: page}))
|
||||
|
@ -50,6 +51,7 @@ def followers(conn, %{"nickname" => nickname, "page" => page}) do
|
|||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
|
||||
{page, _} = Integer.parse(page)
|
||||
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|> json(UserView.render("followers.json", %{user: user, page: page}))
|
||||
|
@ -74,7 +76,9 @@ def outbox(conn, %{"nickname" => nickname, "max_id" => max_id}) do
|
|||
end
|
||||
end
|
||||
|
||||
def outbox(conn, %{"nickname" => nickname}) do outbox(conn, %{"nickname" => nickname, "max_id" => nil}) end
|
||||
def outbox(conn, %{"nickname" => nickname}) do
|
||||
outbox(conn, %{"nickname" => nickname, "max_id" => nil})
|
||||
end
|
||||
|
||||
# TODO: Ensure that this inbox is a recipient of the message
|
||||
def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
|
||||
|
@ -84,7 +88,8 @@ def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
|
|||
|
||||
def inbox(conn, params) do
|
||||
headers = Enum.into(conn.req_headers, %{})
|
||||
if !(String.contains?(headers["signature"] || "", params["actor"])) do
|
||||
|
||||
if !String.contains?(headers["signature"] || "", params["actor"]) do
|
||||
Logger.info("Signature not from author, relayed message, fetching from source")
|
||||
ActivityPub.fetch_object_from_id(params["object"]["id"])
|
||||
else
|
||||
|
|
|
@ -25,21 +25,25 @@ def fix_object(object) do
|
|||
|> fix_tag
|
||||
end
|
||||
|
||||
def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object) when not is_nil(in_reply_to_id) do
|
||||
def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object)
|
||||
when not is_nil(in_reply_to_id) do
|
||||
case ActivityPub.fetch_object_from_id(in_reply_to_id) do
|
||||
{:ok, replied_object} ->
|
||||
activity = Activity.get_create_activity_by_object_ap_id(replied_object.data["id"])
|
||||
|
||||
object
|
||||
|> Map.put("inReplyTo", replied_object.data["id"])
|
||||
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
|
||||
|> Map.put("inReplyToStatusId", activity.id)
|
||||
|> Map.put("conversation", replied_object.data["context"] || object["conversation"])
|
||||
|> Map.put("context", replied_object.data["context"] || object["conversation"])
|
||||
|
||||
e ->
|
||||
Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
|
||||
object
|
||||
end
|
||||
end
|
||||
|
||||
def fix_in_reply_to(object), do: object
|
||||
|
||||
def fix_context(object) do
|
||||
|
@ -48,27 +52,32 @@ def fix_context(object) do
|
|||
end
|
||||
|
||||
def fix_attachments(object) do
|
||||
attachments = (object["attachment"] || [])
|
||||
|> Enum.map(fn (data) ->
|
||||
url = [%{"type" => "Link", "mediaType" => data["mediaType"], "href" => data["url"]}]
|
||||
Map.put(data, "url", url)
|
||||
end)
|
||||
attachments =
|
||||
(object["attachment"] || [])
|
||||
|> Enum.map(fn data ->
|
||||
url = [%{"type" => "Link", "mediaType" => data["mediaType"], "href" => data["url"]}]
|
||||
Map.put(data, "url", url)
|
||||
end)
|
||||
|
||||
object
|
||||
|> Map.put("attachment", attachments)
|
||||
end
|
||||
|
||||
def fix_emoji(object) do
|
||||
tags = (object["tag"] || [])
|
||||
emoji = tags |> Enum.filter(fn (data) -> data["type"] == "Emoji" and data["icon"] end)
|
||||
emoji = emoji |> Enum.reduce(%{}, fn (data, mapping) ->
|
||||
name = data["name"]
|
||||
if String.starts_with?(name, ":") do
|
||||
name = name |> String.slice(1..-2)
|
||||
end
|
||||
tags = object["tag"] || []
|
||||
emoji = tags |> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end)
|
||||
|
||||
mapping |> Map.put(name, data["icon"]["url"])
|
||||
end)
|
||||
emoji =
|
||||
emoji
|
||||
|> Enum.reduce(%{}, fn data, mapping ->
|
||||
name = data["name"]
|
||||
|
||||
if String.starts_with?(name, ":") do
|
||||
name = name |> String.slice(1..-2)
|
||||
end
|
||||
|
||||
mapping |> Map.put(name, data["icon"]["url"])
|
||||
end)
|
||||
|
||||
# we merge mastodon and pleroma emoji into a single mapping, to allow for both wire formats
|
||||
emoji = Map.merge(object["emoji"] || %{}, emoji)
|
||||
|
@ -78,9 +87,10 @@ def fix_emoji(object) do
|
|||
end
|
||||
|
||||
def fix_tag(object) do
|
||||
tags = (object["tag"] || [])
|
||||
|> Enum.filter(fn (data) -> data["type"] == "Hashtag" and data["name"] end)
|
||||
|> Enum.map(fn (data) -> String.slice(data["name"], 1..-1) end)
|
||||
tags =
|
||||
(object["tag"] || [])
|
||||
|> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
|
||||
|> Enum.map(fn data -> String.slice(data["name"], 1..-1) end)
|
||||
|
||||
combined = (object["tag"] || []) ++ tags
|
||||
|
||||
|
@ -103,13 +113,13 @@ def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = obje
|
|||
context: object["conversation"],
|
||||
local: false,
|
||||
published: data["published"],
|
||||
additional: Map.take(data, [
|
||||
"cc",
|
||||
"id"
|
||||
])
|
||||
additional:
|
||||
Map.take(data, [
|
||||
"cc",
|
||||
"id"
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
ActivityPub.create(params)
|
||||
else
|
||||
%Activity{} = activity -> {:ok, activity}
|
||||
|
@ -117,11 +127,14 @@ def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = obje
|
|||
end
|
||||
end
|
||||
|
||||
def handle_incoming(%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data) do
|
||||
def handle_incoming(
|
||||
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data
|
||||
) do
|
||||
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
|
||||
%User{} = follower <- User.get_or_fetch_by_ap_id(follower),
|
||||
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
|
||||
ActivityPub.accept(%{to: [follower.ap_id], actor: followed.ap_id, object: data, local: true})
|
||||
|
||||
User.follow(follower, followed)
|
||||
{:ok, activity}
|
||||
else
|
||||
|
@ -129,7 +142,9 @@ def handle_incoming(%{"type" => "Follow", "object" => followed, "actor" => follo
|
|||
end
|
||||
end
|
||||
|
||||
def handle_incoming(%{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = data) do
|
||||
def handle_incoming(
|
||||
%{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = data
|
||||
) do
|
||||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||
{:ok, activity, object} <- ActivityPub.like(actor, object, id, false) do
|
||||
|
@ -139,7 +154,9 @@ def handle_incoming(%{"type" => "Like", "object" => object_id, "actor" => actor,
|
|||
end
|
||||
end
|
||||
|
||||
def handle_incoming(%{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = data) do
|
||||
def handle_incoming(
|
||||
%{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = data
|
||||
) do
|
||||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||
{:ok, activity, object} <- ActivityPub.announce(actor, object, id, false) do
|
||||
|
@ -149,20 +166,31 @@ def handle_incoming(%{"type" => "Announce", "object" => object_id, "actor" => ac
|
|||
end
|
||||
end
|
||||
|
||||
def handle_incoming(%{"type" => "Update", "object" => %{"type" => "Person"} = object, "actor" => actor_id} = data) do
|
||||
def handle_incoming(
|
||||
%{"type" => "Update", "object" => %{"type" => "Person"} = object, "actor" => actor_id} =
|
||||
data
|
||||
) do
|
||||
with %User{ap_id: ^actor_id} = actor <- User.get_by_ap_id(object["id"]) do
|
||||
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
|
||||
|
||||
banner = new_user_data[:info]["banner"]
|
||||
update_data = new_user_data
|
||||
|> Map.take([:name, :bio, :avatar])
|
||||
|> Map.put(:info, Map.merge(actor.info, %{"banner" => banner}))
|
||||
|
||||
update_data =
|
||||
new_user_data
|
||||
|> Map.take([:name, :bio, :avatar])
|
||||
|> Map.put(:info, Map.merge(actor.info, %{"banner" => banner}))
|
||||
|
||||
actor
|
||||
|> User.upgrade_changeset(update_data)
|
||||
|> User.update_and_set_cache()
|
||||
|
||||
ActivityPub.update(%{local: false, to: data["to"] || [], cc: data["cc"] || [], object: object, actor: actor_id})
|
||||
ActivityPub.update(%{
|
||||
local: false,
|
||||
to: data["to"] || [],
|
||||
cc: data["cc"] || [],
|
||||
object: object,
|
||||
actor: actor_id
|
||||
})
|
||||
else
|
||||
e ->
|
||||
Logger.error(e)
|
||||
|
@ -171,11 +199,15 @@ def handle_incoming(%{"type" => "Update", "object" => %{"type" => "Person"} = ob
|
|||
end
|
||||
|
||||
# TODO: Make secure.
|
||||
def handle_incoming(%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data) do
|
||||
object_id = case object_id do
|
||||
%{"id" => id} -> id
|
||||
id -> id
|
||||
end
|
||||
def handle_incoming(
|
||||
%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data
|
||||
) do
|
||||
object_id =
|
||||
case object_id do
|
||||
%{"id" => id} -> id
|
||||
id -> id
|
||||
end
|
||||
|
||||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||
{:ok, activity} <- ActivityPub.delete(object, false) do
|
||||
|
@ -203,6 +235,7 @@ def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) do
|
|||
_e -> object
|
||||
end
|
||||
end
|
||||
|
||||
def set_reply_to_uri(obj), do: obj
|
||||
|
||||
# Prepares the object of an outgoing create activity.
|
||||
|
@ -222,20 +255,25 @@ def prepare_object(object) do
|
|||
"""
|
||||
internal -> Mastodon
|
||||
"""
|
||||
|
||||
def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
|
||||
object = object
|
||||
|> prepare_object
|
||||
data = data
|
||||
|> Map.put("object", object)
|
||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||
object =
|
||||
object
|
||||
|> prepare_object
|
||||
|
||||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
|
||||
def prepare_outgoing(%{"type" => type} = data) do
|
||||
data = data
|
||||
|> maybe_fix_object_url
|
||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||
data =
|
||||
data
|
||||
|> maybe_fix_object_url
|
||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
|
@ -245,11 +283,13 @@ def maybe_fix_object_url(data) do
|
|||
case ActivityPub.fetch_object_from_id(data["object"]) do
|
||||
{:ok, relative_object} ->
|
||||
if relative_object.data["external_url"] do
|
||||
data = data
|
||||
|> Map.put("object", relative_object.data["external_url"])
|
||||
data =
|
||||
data
|
||||
|> Map.put("object", relative_object.data["external_url"])
|
||||
else
|
||||
data
|
||||
end
|
||||
|
||||
e ->
|
||||
Logger.error("Couldn't fetch #{data["object"]} #{inspect(e)}")
|
||||
data
|
||||
|
@ -260,8 +300,15 @@ def maybe_fix_object_url(data) do
|
|||
end
|
||||
|
||||
def add_hashtags(object) do
|
||||
tags = (object["tag"] || [])
|
||||
|> Enum.map fn (tag) -> %{"href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}", "name" => "##{tag}", "type" => "Hashtag"} end
|
||||
tags =
|
||||
(object["tag"] || [])
|
||||
|> Enum.map(fn tag ->
|
||||
%{
|
||||
"href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}",
|
||||
"name" => "##{tag}",
|
||||
"type" => "Hashtag"
|
||||
}
|
||||
end)
|
||||
|
||||
object
|
||||
|> Map.put("tag", tags)
|
||||
|
@ -269,10 +316,14 @@ def add_hashtags(object) do
|
|||
|
||||
def add_mention_tags(object) do
|
||||
recipients = object["to"] ++ (object["cc"] || [])
|
||||
mentions = recipients
|
||||
|> Enum.map(fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end)
|
||||
|> Enum.filter(&(&1))
|
||||
|> Enum.map(fn(user) -> %{"type" => "Mention", "href" => user.ap_id, "name" => "@#{user.nickname}"} end)
|
||||
|
||||
mentions =
|
||||
recipients
|
||||
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.map(fn user ->
|
||||
%{"type" => "Mention", "href" => user.ap_id, "name" => "@#{user.nickname}"}
|
||||
end)
|
||||
|
||||
tags = object["tag"] || []
|
||||
|
||||
|
@ -284,13 +335,18 @@ def add_mention_tags(object) do
|
|||
def add_emoji_tags(object) do
|
||||
tags = object["tag"] || []
|
||||
emoji = object["emoji"] || []
|
||||
out = emoji |> Enum.map(fn {name, url} ->
|
||||
%{"icon" => %{"url" => url, "type" => "Image"},
|
||||
"name" => ":" <> name <> ":",
|
||||
"type" => "Emoji",
|
||||
"updated" => "1970-01-01T00:00:00Z",
|
||||
"id" => url}
|
||||
end)
|
||||
|
||||
out =
|
||||
emoji
|
||||
|> Enum.map(fn {name, url} ->
|
||||
%{
|
||||
"icon" => %{"url" => url, "type" => "Image"},
|
||||
"name" => ":" <> name <> ":",
|
||||
"type" => "Emoji",
|
||||
"updated" => "1970-01-01T00:00:00Z",
|
||||
"id" => url
|
||||
}
|
||||
end)
|
||||
|
||||
object
|
||||
|> Map.put("tag", tags ++ out)
|
||||
|
@ -313,11 +369,12 @@ def add_attributed_to(object) do
|
|||
end
|
||||
|
||||
def prepare_attachments(object) do
|
||||
attachments = (object["attachment"] || [])
|
||||
|> Enum.map(fn (data) ->
|
||||
[%{"mediaType" => media_type, "href" => href} | _] = data["url"]
|
||||
%{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"}
|
||||
end)
|
||||
attachments =
|
||||
(object["attachment"] || [])
|
||||
|> Enum.map(fn data ->
|
||||
[%{"mediaType" => media_type, "href" => href} | _] = data["url"]
|
||||
%{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"}
|
||||
end)
|
||||
|
||||
object
|
||||
|> Map.put("attachment", attachments)
|
||||
|
@ -325,9 +382,24 @@ def prepare_attachments(object) do
|
|||
|
||||
defp user_upgrade_task(user) do
|
||||
old_follower_address = User.ap_followers(user)
|
||||
q = from u in User,
|
||||
where: ^old_follower_address in u.following,
|
||||
update: [set: [following: fragment("array_replace(?,?,?)", u.following, ^old_follower_address, ^user.follower_address)]]
|
||||
|
||||
q =
|
||||
from(
|
||||
u in User,
|
||||
where: ^old_follower_address in u.following,
|
||||
update: [
|
||||
set: [
|
||||
following:
|
||||
fragment(
|
||||
"array_replace(?,?,?)",
|
||||
u.following,
|
||||
^old_follower_address,
|
||||
^user.follower_address
|
||||
)
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
Repo.update_all(q, [])
|
||||
|
||||
maybe_retire_websub(user.ap_id)
|
||||
|
@ -335,22 +407,40 @@ defp user_upgrade_task(user) do
|
|||
# Only do this for recent activties, don't go through the whole db.
|
||||
# Only look at the last 1000 activities.
|
||||
since = (Repo.aggregate(Activity, :max, :id) || 0) - 1_000
|
||||
q = from a in Activity,
|
||||
where: ^old_follower_address in a.recipients,
|
||||
where: a.id > ^since,
|
||||
update: [set: [recipients: fragment("array_replace(?,?,?)", a.recipients, ^old_follower_address, ^user.follower_address)]]
|
||||
|
||||
q =
|
||||
from(
|
||||
a in Activity,
|
||||
where: ^old_follower_address in a.recipients,
|
||||
where: a.id > ^since,
|
||||
update: [
|
||||
set: [
|
||||
recipients:
|
||||
fragment(
|
||||
"array_replace(?,?,?)",
|
||||
a.recipients,
|
||||
^old_follower_address,
|
||||
^user.follower_address
|
||||
)
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
Repo.update_all(q, [])
|
||||
end
|
||||
|
||||
def upgrade_user_from_ap_id(ap_id, async \\ true) do
|
||||
with %User{local: false} = user <- User.get_by_ap_id(ap_id),
|
||||
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||
data = data
|
||||
|> Map.put(:info, Map.merge(user.info, data[:info]))
|
||||
data =
|
||||
data
|
||||
|> Map.put(:info, Map.merge(user.info, data[:info]))
|
||||
|
||||
already_ap = User.ap_enabled?(user)
|
||||
{:ok, user} = User.upgrade_changeset(user, data)
|
||||
|> Repo.update()
|
||||
|
||||
{:ok, user} =
|
||||
User.upgrade_changeset(user, data)
|
||||
|> Repo.update()
|
||||
|
||||
if !already_ap do
|
||||
# This could potentially take a long time, do it in the background
|
||||
|
@ -371,9 +461,13 @@ def upgrade_user_from_ap_id(ap_id, async \\ true) do
|
|||
|
||||
def maybe_retire_websub(ap_id) do
|
||||
# some sanity checks
|
||||
if is_binary(ap_id) && (String.length(ap_id) > 8) do
|
||||
q = from ws in Pleroma.Web.Websub.WebsubClientSubscription,
|
||||
where: fragment("? like ?", ws.topic, ^"#{ap_id}%")
|
||||
if is_binary(ap_id) && String.length(ap_id) > 8 do
|
||||
q =
|
||||
from(
|
||||
ws in Pleroma.Web.Websub.WebsubClientSubscription,
|
||||
where: fragment("? like ?", ws.topic, ^"#{ap_id}%")
|
||||
)
|
||||
|
||||
Repo.delete_all(q)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,7 +26,7 @@ def make_json_ld_header do
|
|||
end
|
||||
|
||||
def make_date do
|
||||
DateTime.utc_now() |> DateTime.to_iso8601
|
||||
DateTime.utc_now() |> DateTime.to_iso8601()
|
||||
end
|
||||
|
||||
def generate_activity_id do
|
||||
|
@ -38,25 +38,28 @@ def generate_context_id do
|
|||
end
|
||||
|
||||
def generate_object_id do
|
||||
Helpers.o_status_url(Endpoint, :object, UUID.generate)
|
||||
Helpers.o_status_url(Endpoint, :object, UUID.generate())
|
||||
end
|
||||
|
||||
def generate_id(type) do
|
||||
"#{Web.base_url()}/#{type}/#{UUID.generate}"
|
||||
"#{Web.base_url()}/#{type}/#{UUID.generate()}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Enqueues an activity for federation if it's local
|
||||
"""
|
||||
def maybe_federate(%Activity{local: true} = activity) do
|
||||
priority = case activity.data["type"] do
|
||||
"Delete" -> 10
|
||||
"Create" -> 1
|
||||
_ -> 5
|
||||
end
|
||||
priority =
|
||||
case activity.data["type"] do
|
||||
"Delete" -> 10
|
||||
"Create" -> 1
|
||||
_ -> 5
|
||||
end
|
||||
|
||||
Pleroma.Web.Federator.enqueue(:publish, activity, priority)
|
||||
:ok
|
||||
end
|
||||
|
||||
def maybe_federate(_), do: :ok
|
||||
|
||||
@doc """
|
||||
|
@ -64,9 +67,10 @@ def maybe_federate(_), do: :ok
|
|||
also adds it to an included object
|
||||
"""
|
||||
def lazy_put_activity_defaults(map) do
|
||||
map = map
|
||||
|> Map.put_new_lazy("id", &generate_activity_id/0)
|
||||
|> Map.put_new_lazy("published", &make_date/0)
|
||||
map =
|
||||
map
|
||||
|> Map.put_new_lazy("id", &generate_activity_id/0)
|
||||
|> Map.put_new_lazy("published", &make_date/0)
|
||||
|
||||
if is_map(map["object"]) do
|
||||
object = lazy_put_object_defaults(map["object"])
|
||||
|
@ -88,11 +92,13 @@ def lazy_put_object_defaults(map) do
|
|||
@doc """
|
||||
Inserts a full object if it is contained in an activity.
|
||||
"""
|
||||
def insert_full_object(%{"object" => %{"type" => type} = object_data}) when is_map(object_data) and type in ["Note"] do
|
||||
def insert_full_object(%{"object" => %{"type" => type} = object_data})
|
||||
when is_map(object_data) and type in ["Note"] do
|
||||
with {:ok, _} <- Object.create(object_data) do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
def insert_full_object(_), do: :ok
|
||||
|
||||
def update_object_in_activities(%{data: %{"id" => id}} = object) do
|
||||
|
@ -101,7 +107,8 @@ def update_object_in_activities(%{data: %{"id" => id}} = object) do
|
|||
# Alternatively, just don't do this and fetch the current object each time. Most
|
||||
# could probably be taken from cache.
|
||||
relevant_activities = Activity.all_by_object_ap_id(id)
|
||||
Enum.map(relevant_activities, fn (activity) ->
|
||||
|
||||
Enum.map(relevant_activities, fn activity ->
|
||||
new_activity_data = activity.data |> Map.put("object", object.data)
|
||||
changeset = Changeset.change(activity, data: new_activity_data)
|
||||
Repo.update(changeset)
|
||||
|
@ -114,11 +121,20 @@ def update_object_in_activities(%{data: %{"id" => id}} = object) do
|
|||
Returns an existing like if a user already liked an object
|
||||
"""
|
||||
def get_existing_like(actor, %{data: %{"id" => id}}) do
|
||||
query = from activity in Activity,
|
||||
where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
|
||||
# this is to use the index
|
||||
where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ?", activity.data, activity.data, ^id),
|
||||
where: fragment("(?)->>'type' = 'Like'", activity.data)
|
||||
query =
|
||||
from(
|
||||
activity in Activity,
|
||||
where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
|
||||
# this is to use the index
|
||||
where:
|
||||
fragment(
|
||||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
|
||||
activity.data,
|
||||
activity.data,
|
||||
^id
|
||||
),
|
||||
where: fragment("(?)->>'type' = 'Like'", activity.data)
|
||||
)
|
||||
|
||||
Repo.one(query)
|
||||
end
|
||||
|
@ -137,10 +153,12 @@ def make_like_data(%User{ap_id: ap_id} = actor, %{data: %{"id" => id}} = object,
|
|||
end
|
||||
|
||||
def update_element_in_object(property, element, object) do
|
||||
with new_data <- object.data |> Map.put("#{property}_count", length(element)) |> Map.put("#{property}s", element),
|
||||
with new_data <-
|
||||
object.data |> Map.put("#{property}_count", length(element))
|
||||
|> Map.put("#{property}s", element),
|
||||
changeset <- Changeset.change(object, data: new_data),
|
||||
{:ok, object} <- Repo.update(changeset),
|
||||
_ <- update_object_in_activities(object) do
|
||||
_ <- update_object_in_activities(object) do
|
||||
{:ok, object}
|
||||
end
|
||||
end
|
||||
|
@ -150,7 +168,7 @@ def update_likes_in_object(likes, object) do
|
|||
end
|
||||
|
||||
def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do
|
||||
with likes <- [actor | (object.data["likes"] || [])] |> Enum.uniq do
|
||||
with likes <- [actor | object.data["likes"] || []] |> Enum.uniq() do
|
||||
update_likes_in_object(likes, object)
|
||||
end
|
||||
end
|
||||
|
@ -178,13 +196,20 @@ def make_follow_data(%User{ap_id: follower_id}, %User{ap_id: followed_id}, activ
|
|||
if activity_id, do: Map.put(data, "id", activity_id), else: data
|
||||
end
|
||||
|
||||
def fetch_latest_follow(%User{ap_id: follower_id},
|
||||
%User{ap_id: followed_id}) do
|
||||
query = from activity in Activity,
|
||||
where: fragment("? @> ?", activity.data, ^%{type: "Follow", actor: follower_id,
|
||||
object: followed_id}),
|
||||
order_by: [desc: :id],
|
||||
limit: 1
|
||||
def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do
|
||||
query =
|
||||
from(
|
||||
activity in Activity,
|
||||
where:
|
||||
fragment(
|
||||
"? @> ?",
|
||||
activity.data,
|
||||
^%{type: "Follow", actor: follower_id, object: followed_id}
|
||||
),
|
||||
order_by: [desc: :id],
|
||||
limit: 1
|
||||
)
|
||||
|
||||
Repo.one(query)
|
||||
end
|
||||
|
||||
|
@ -193,7 +218,11 @@ def fetch_latest_follow(%User{ap_id: follower_id},
|
|||
@doc """
|
||||
Make announce activity data for the given actor and object
|
||||
"""
|
||||
def make_announce_data(%User{ap_id: ap_id} = user, %Object{data: %{"id" => id}} = object, activity_id) do
|
||||
def make_announce_data(
|
||||
%User{ap_id: ap_id} = user,
|
||||
%Object{data: %{"id" => id}} = object,
|
||||
activity_id
|
||||
) do
|
||||
data = %{
|
||||
"type" => "Announce",
|
||||
"actor" => ap_id,
|
||||
|
@ -207,7 +236,7 @@ def make_announce_data(%User{ap_id: ap_id} = user, %Object{data: %{"id" => id}}
|
|||
end
|
||||
|
||||
def add_announce_to_object(%Activity{data: %{"actor" => actor}}, object) do
|
||||
with announcements <- [actor | (object.data["announcements"] || [])] |> Enum.uniq do
|
||||
with announcements <- [actor | object.data["announcements"] || []] |> Enum.uniq() do
|
||||
update_element_in_object("announcement", announcements, object)
|
||||
end
|
||||
end
|
||||
|
@ -223,14 +252,14 @@ def make_unfollow_data(follower, followed, follow_activity) do
|
|||
}
|
||||
end
|
||||
|
||||
|
||||
#### Create-related helpers
|
||||
|
||||
def make_create_data(params, additional) do
|
||||
published = params.published || make_date()
|
||||
|
||||
%{
|
||||
"type" => "Create",
|
||||
"to" => params.to |> Enum.uniq,
|
||||
"to" => params.to |> Enum.uniq(),
|
||||
"actor" => params.actor.ap_id,
|
||||
"object" => params.object,
|
||||
"published" => published,
|
||||
|
|
|
@ -12,6 +12,7 @@ def render("user.json", %{user: user}) do
|
|||
{: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_encode([public_key])
|
||||
|
||||
%{
|
||||
"id" => user.ap_id,
|
||||
"type" => "Person",
|
||||
|
@ -30,7 +31,7 @@ def render("user.json", %{user: user}) do
|
|||
"publicKeyPem" => public_key
|
||||
},
|
||||
"endpoints" => %{
|
||||
"sharedInbox" => "#{Pleroma.Web.Endpoint.url}/inbox"
|
||||
"sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox"
|
||||
},
|
||||
"icon" => %{
|
||||
"type" => "Image",
|
||||
|
@ -47,7 +48,8 @@ def render("user.json", %{user: user}) do
|
|||
def collection(collection, iri, page) do
|
||||
offset = (page - 1) * 10
|
||||
items = Enum.slice(collection, offset, 10)
|
||||
items = Enum.map(items, fn (user) -> user.ap_id end)
|
||||
items = Enum.map(items, fn user -> user.ap_id end)
|
||||
|
||||
map = %{
|
||||
"id" => "#{iri}?page=#{page}",
|
||||
"type" => "OrderedCollectionPage",
|
||||
|
@ -55,19 +57,22 @@ def collection(collection, iri, page) do
|
|||
"totalItems" => length(collection),
|
||||
"orderedItems" => items
|
||||
}
|
||||
|
||||
if offset < length(collection) do
|
||||
Map.put(map, "next", "#{iri}?page=#{page+1}")
|
||||
Map.put(map, "next", "#{iri}?page=#{page + 1}")
|
||||
end
|
||||
end
|
||||
|
||||
def render("following.json", %{user: user, page: page}) do
|
||||
{:ok, following} = User.get_friends(user)
|
||||
|
||||
collection(following, "#{user.ap_id}/following", page)
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render("following.json", %{user: user}) do
|
||||
{:ok, following} = User.get_friends(user)
|
||||
|
||||
%{
|
||||
"id" => "#{user.ap_id}/following",
|
||||
"type" => "OrderedCollection",
|
||||
|
@ -79,12 +84,14 @@ def render("following.json", %{user: user}) do
|
|||
|
||||
def render("followers.json", %{user: user, page: page}) do
|
||||
{:ok, followers} = User.get_followers(user)
|
||||
|
||||
collection(followers, "#{user.ap_id}/followers", page)
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render("followers.json", %{user: user}) do
|
||||
{:ok, followers} = User.get_followers(user)
|
||||
|
||||
%{
|
||||
"id" => "#{user.ap_id}/followers",
|
||||
"type" => "OrderedCollection",
|
||||
|
@ -115,19 +122,21 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do
|
|||
activities = Enum.reverse(activities)
|
||||
max_id = Enum.at(activities, 0).id
|
||||
|
||||
collection = Enum.map(activities, fn (act) ->
|
||||
{:ok, data} = Transmogrifier.prepare_outgoing(act.data)
|
||||
data
|
||||
end)
|
||||
collection =
|
||||
Enum.map(activities, fn act ->
|
||||
{:ok, data} = Transmogrifier.prepare_outgoing(act.data)
|
||||
data
|
||||
end)
|
||||
|
||||
iri = "#{user.ap_id}/outbox"
|
||||
|
||||
page = %{
|
||||
"id" => "#{iri}?max_id=#{max_id}",
|
||||
"type" => "OrderedCollectionPage",
|
||||
"partOf" => iri,
|
||||
"totalItems" => info.note_count,
|
||||
"orderedItems" => collection,
|
||||
"next" => "#{iri}?max_id=#{min_id-1}",
|
||||
"next" => "#{iri}?max_id=#{min_id - 1}"
|
||||
}
|
||||
|
||||
if max_qid == nil do
|
||||
|
|
|
@ -6,11 +6,11 @@ defmodule Pleroma.Web.UserSocket do
|
|||
## Channels
|
||||
# channel "room:*", Pleroma.Web.RoomChannel
|
||||
if Application.get_env(:pleroma, :chat) |> Keyword.get(:enabled) do
|
||||
channel "chat:*", Pleroma.Web.ChatChannel
|
||||
channel("chat:*", Pleroma.Web.ChatChannel)
|
||||
end
|
||||
|
||||
## Transports
|
||||
transport :websocket, Phoenix.Transports.WebSocket
|
||||
transport(:websocket, Phoenix.Transports.WebSocket)
|
||||
# transport :longpoll, Phoenix.Transports.LongPoll
|
||||
|
||||
# Socket params are passed from the client and can
|
||||
|
|
|
@ -9,19 +9,21 @@ def join("chat:public", _message, socket) do
|
|||
end
|
||||
|
||||
def handle_info(:after_join, socket) do
|
||||
push socket, "messages", %{messages: ChatChannelState.messages()}
|
||||
push(socket, "messages", %{messages: ChatChannelState.messages()})
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} = socket) do
|
||||
text = String.trim(text)
|
||||
|
||||
if String.length(text) > 0 do
|
||||
author = User.get_cached_by_nickname(user_name)
|
||||
author = Pleroma.Web.MastodonAPI.AccountView.render("account.json", user: author)
|
||||
message = ChatChannelState.add_message(%{text: text, author: author})
|
||||
|
||||
broadcast! socket, "new_msg", message
|
||||
broadcast!(socket, "new_msg", message)
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
@ -43,6 +45,6 @@ def add_message(message) do
|
|||
end
|
||||
|
||||
def messages() do
|
||||
Agent.get(__MODULE__, fn state -> state[:messages] |> Enum.reverse end)
|
||||
Agent.get(__MODULE__, fn state -> state[:messages] |> Enum.reverse() end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
def delete(activity_id, user) do
|
||||
with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id),
|
||||
%Object{} = object <- Object.get_by_ap_id(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}
|
||||
end
|
||||
|
@ -46,17 +46,22 @@ def unfavorite(id_or_ap_id, user) do
|
|||
end
|
||||
end
|
||||
|
||||
def get_visibility(%{"visibility" => visibility}) when visibility in ~w{public unlisted private direct}, do: visibility
|
||||
def get_visibility(%{"visibility" => visibility})
|
||||
when visibility in ~w{public unlisted private direct},
|
||||
do: visibility
|
||||
|
||||
def get_visibility(%{"in_reply_to_status_id" => status_id}) when not is_nil(status_id) do
|
||||
inReplyTo = get_replied_to_activity(status_id)
|
||||
Pleroma.Web.MastodonAPI.StatusView.get_visibility(inReplyTo.data["object"])
|
||||
end
|
||||
|
||||
def get_visibility(_), do: "public"
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@limit Keyword.get(@instance, :limit)
|
||||
def post(user, %{"status" => status} = data) do
|
||||
visibility = get_visibility(data)
|
||||
|
||||
with status <- String.trim(status),
|
||||
length when length in 1..@limit <- String.length(status),
|
||||
attachments <- attachments_from_ids(data["media_ids"]),
|
||||
|
@ -64,18 +69,52 @@ def post(user, %{"status" => status} = data) do
|
|||
inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]),
|
||||
{to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility),
|
||||
tags <- Formatter.parse_tags(status, data),
|
||||
content_html <- make_content_html(status, mentions, attachments, tags, data["no_attachment_links"]),
|
||||
content_html <-
|
||||
make_content_html(status, mentions, attachments, tags, data["no_attachment_links"]),
|
||||
context <- make_context(inReplyTo),
|
||||
cw <- data["spoiler_text"],
|
||||
object <- make_note_data(user.ap_id, to, context, content_html, attachments, inReplyTo, tags, cw, cc),
|
||||
object <- Map.put(object, "emoji", Formatter.get_emoji(status) |> Enum.reduce(%{}, fn({name, file}, acc) -> Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url}#{file}") end)) do
|
||||
res = ActivityPub.create(%{to: to, actor: user, context: context, object: object, additional: %{"cc" => cc}})
|
||||
object <-
|
||||
make_note_data(
|
||||
user.ap_id,
|
||||
to,
|
||||
context,
|
||||
content_html,
|
||||
attachments,
|
||||
inReplyTo,
|
||||
tags,
|
||||
cw,
|
||||
cc
|
||||
),
|
||||
object <-
|
||||
Map.put(
|
||||
object,
|
||||
"emoji",
|
||||
Formatter.get_emoji(status)
|
||||
|> Enum.reduce(%{}, fn {name, file}, acc ->
|
||||
Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
|
||||
end)
|
||||
) do
|
||||
res =
|
||||
ActivityPub.create(%{
|
||||
to: to,
|
||||
actor: user,
|
||||
context: context,
|
||||
object: object,
|
||||
additional: %{"cc" => cc}
|
||||
})
|
||||
|
||||
User.increase_note_count(user)
|
||||
res
|
||||
end
|
||||
end
|
||||
|
||||
def update(user) do
|
||||
ActivityPub.update(%{local: true, to: [user.follower_address], cc: [], actor: user.ap_id, object: Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})})
|
||||
ActivityPub.update(%{
|
||||
local: true,
|
||||
to: [user.follower_address],
|
||||
cc: [],
|
||||
actor: user.ap_id,
|
||||
object: Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
# This is a hack for twidere.
|
||||
def get_by_id_or_ap_id(id) do
|
||||
activity = Repo.get(Activity, id) || Activity.get_create_activity_by_object_ap_id(id)
|
||||
|
||||
if activity.data["type"] == "Create" do
|
||||
activity
|
||||
else
|
||||
|
@ -16,10 +17,11 @@ def get_by_id_or_ap_id(id) do
|
|||
def get_replied_to_activity(id) when not is_nil(id) do
|
||||
Repo.get(Activity, id)
|
||||
end
|
||||
|
||||
def get_replied_to_activity(_), do: nil
|
||||
|
||||
def attachments_from_ids(ids) do
|
||||
Enum.map(ids || [], fn (media_id) ->
|
||||
Enum.map(ids || [], fn media_id ->
|
||||
Repo.get(Object, media_id).data
|
||||
end)
|
||||
end
|
||||
|
@ -27,8 +29,9 @@ def attachments_from_ids(ids) do
|
|||
def to_for_user_and_mentions(user, mentions, inReplyTo, "public") do
|
||||
to = ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
|
||||
mentioned_users = Enum.map(mentions, fn ({_, %{ap_id: ap_id}}) -> ap_id end)
|
||||
mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end)
|
||||
cc = [user.follower_address | mentioned_users]
|
||||
|
||||
if inReplyTo do
|
||||
{to, Enum.uniq([inReplyTo.data["actor"] | cc])}
|
||||
else
|
||||
|
@ -47,7 +50,8 @@ def to_for_user_and_mentions(user, mentions, inReplyTo, "private") do
|
|||
end
|
||||
|
||||
def to_for_user_and_mentions(user, mentions, inReplyTo, "direct") do
|
||||
mentioned_users = Enum.map(mentions, fn ({_, %{ap_id: ap_id}}) -> ap_id end)
|
||||
mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end)
|
||||
|
||||
if inReplyTo do
|
||||
{Enum.uniq([inReplyTo.data["actor"] | mentioned_users]), []}
|
||||
else
|
||||
|
@ -62,55 +66,72 @@ def make_content_html(status, mentions, attachments, tags, no_attachment_links \
|
|||
end
|
||||
|
||||
def make_context(%Activity{data: %{"context" => context}}), do: context
|
||||
def make_context(_), do: Utils.generate_context_id
|
||||
def make_context(_), do: Utils.generate_context_id()
|
||||
|
||||
def maybe_add_attachments(text, attachments, _no_links = true), do: text
|
||||
|
||||
def maybe_add_attachments(text, attachments, _no_links) do
|
||||
add_attachments(text, attachments)
|
||||
end
|
||||
|
||||
def add_attachments(text, attachments) do
|
||||
attachment_text = Enum.map(attachments, fn
|
||||
(%{"url" => [%{"href" => href} | _]}) ->
|
||||
name = URI.decode(Path.basename(href))
|
||||
"<a href=\"#{href}\" class='attachment'>#{shortname(name)}</a>"
|
||||
_ -> ""
|
||||
end)
|
||||
attachment_text =
|
||||
Enum.map(attachments, fn
|
||||
%{"url" => [%{"href" => href} | _]} ->
|
||||
name = URI.decode(Path.basename(href))
|
||||
"<a href=\"#{href}\" class='attachment'>#{shortname(name)}</a>"
|
||||
|
||||
_ ->
|
||||
""
|
||||
end)
|
||||
|
||||
Enum.join([text | attachment_text], "<br>")
|
||||
end
|
||||
|
||||
def format_input(text, mentions, tags) do
|
||||
text
|
||||
|> Formatter.html_escape
|
||||
|> Formatter.html_escape()
|
||||
|> String.replace("\n", "<br>")
|
||||
|> (&({[], &1})).()
|
||||
|> Formatter.add_links
|
||||
|> (&{[], &1}).()
|
||||
|> Formatter.add_links()
|
||||
|> Formatter.add_user_links(mentions)
|
||||
|> Formatter.add_hashtag_links(tags)
|
||||
|> Formatter.finalize
|
||||
|> Formatter.finalize()
|
||||
end
|
||||
|
||||
def add_tag_links(text, tags) do
|
||||
tags = tags
|
||||
|> Enum.sort_by(fn ({tag, _}) -> -String.length(tag) end)
|
||||
tags =
|
||||
tags
|
||||
|> Enum.sort_by(fn {tag, _} -> -String.length(tag) end)
|
||||
|
||||
Enum.reduce(tags, text, fn({full, tag}, text) ->
|
||||
url = "#<a href='#{Pleroma.Web.base_url}/tag/#{tag}' rel='tag'>#{tag}</a>"
|
||||
Enum.reduce(tags, text, fn {full, tag}, text ->
|
||||
url = "#<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>#{tag}</a>"
|
||||
String.replace(text, full, url)
|
||||
end)
|
||||
end
|
||||
|
||||
def make_note_data(actor, to, context, content_html, attachments, inReplyTo, tags, cw \\ nil, cc \\ []) do
|
||||
object = %{
|
||||
"type" => "Note",
|
||||
"to" => to,
|
||||
"cc" => cc,
|
||||
"content" => content_html,
|
||||
"summary" => cw,
|
||||
"context" => context,
|
||||
"attachment" => attachments,
|
||||
"actor" => actor,
|
||||
"tag" => tags |> Enum.map(fn ({_, tag}) -> tag end)
|
||||
}
|
||||
def make_note_data(
|
||||
actor,
|
||||
to,
|
||||
context,
|
||||
content_html,
|
||||
attachments,
|
||||
inReplyTo,
|
||||
tags,
|
||||
cw \\ nil,
|
||||
cc \\ []
|
||||
) do
|
||||
object = %{
|
||||
"type" => "Note",
|
||||
"to" => to,
|
||||
"cc" => cc,
|
||||
"content" => content_html,
|
||||
"summary" => cw,
|
||||
"context" => context,
|
||||
"attachment" => attachments,
|
||||
"actor" => actor,
|
||||
"tag" => tags |> Enum.map(fn {_, tag} -> tag end)
|
||||
}
|
||||
|
||||
if inReplyTo do
|
||||
object
|
||||
|
@ -130,24 +151,25 @@ def format_asctime(date) do
|
|||
end
|
||||
|
||||
def date_to_asctime(date) do
|
||||
with {:ok, date, _offset} <- date |> DateTime.from_iso8601 do
|
||||
with {:ok, date, _offset} <- date |> DateTime.from_iso8601() do
|
||||
format_asctime(date)
|
||||
else _e ->
|
||||
else
|
||||
_e ->
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def to_masto_date(%NaiveDateTime{} = date) do
|
||||
date
|
||||
|> NaiveDateTime.to_iso8601
|
||||
|> NaiveDateTime.to_iso8601()
|
||||
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
|
||||
end
|
||||
|
||||
def to_masto_date(date) do
|
||||
try do
|
||||
date
|
||||
|> NaiveDateTime.from_iso8601!
|
||||
|> NaiveDateTime.to_iso8601
|
||||
|> NaiveDateTime.from_iso8601!()
|
||||
|> NaiveDateTime.to_iso8601()
|
||||
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
|
||||
rescue
|
||||
_e -> ""
|
||||
|
|
|
@ -2,47 +2,55 @@ defmodule Pleroma.Web.Endpoint do
|
|||
use Phoenix.Endpoint, otp_app: :pleroma
|
||||
|
||||
if Application.get_env(:pleroma, :chat) |> Keyword.get(:enabled) do
|
||||
socket "/socket", Pleroma.Web.UserSocket
|
||||
socket("/socket", Pleroma.Web.UserSocket)
|
||||
end
|
||||
socket "/api/v1", Pleroma.Web.MastodonAPI.MastodonSocket
|
||||
|
||||
socket("/api/v1", Pleroma.Web.MastodonAPI.MastodonSocket)
|
||||
|
||||
# Serve at "/" the static files from "priv/static" directory.
|
||||
#
|
||||
# You should set gzip to true if you are running phoenix.digest
|
||||
# when deploying your static files in production.
|
||||
plug Plug.Static,
|
||||
at: "/media", from: "uploads", gzip: false
|
||||
plug Plug.Static,
|
||||
at: "/", from: :pleroma,
|
||||
plug(Plug.Static, at: "/media", from: "uploads", gzip: false)
|
||||
|
||||
plug(
|
||||
Plug.Static,
|
||||
at: "/",
|
||||
from: :pleroma,
|
||||
only: ~w(index.html static finmoji emoji packs sounds images instance sw.js)
|
||||
)
|
||||
|
||||
# Code reloading can be explicitly enabled under the
|
||||
# :code_reloader configuration of your endpoint.
|
||||
if code_reloading? do
|
||||
plug Phoenix.CodeReloader
|
||||
plug(Phoenix.CodeReloader)
|
||||
end
|
||||
|
||||
plug TrailingFormatPlug
|
||||
plug Plug.RequestId
|
||||
plug Plug.Logger
|
||||
plug(TrailingFormatPlug)
|
||||
plug(Plug.RequestId)
|
||||
plug(Plug.Logger)
|
||||
|
||||
plug Plug.Parsers,
|
||||
plug(
|
||||
Plug.Parsers,
|
||||
parsers: [:urlencoded, :multipart, :json],
|
||||
pass: ["*/*"],
|
||||
json_decoder: Jason
|
||||
)
|
||||
|
||||
plug Plug.MethodOverride
|
||||
plug Plug.Head
|
||||
plug(Plug.MethodOverride)
|
||||
plug(Plug.Head)
|
||||
|
||||
# The session will be stored in the cookie and signed,
|
||||
# this means its contents can be read but not tampered with.
|
||||
# Set :encryption_salt if you would also like to encrypt it.
|
||||
plug Plug.Session,
|
||||
plug(
|
||||
Plug.Session,
|
||||
store: :cookie,
|
||||
key: "_pleroma_key",
|
||||
signing_salt: "CqaoopA2"
|
||||
)
|
||||
|
||||
plug Pleroma.Web.Router
|
||||
plug(Pleroma.Web.Router)
|
||||
|
||||
@doc """
|
||||
Dynamically loads configuration from the system environment
|
||||
|
|
|
@ -16,27 +16,36 @@ defmodule Pleroma.Web.Federator do
|
|||
|
||||
def start_link do
|
||||
spawn(fn ->
|
||||
Process.sleep(1000 * 60 * 1) # 1 minute
|
||||
# 1 minute
|
||||
Process.sleep(1000 * 60 * 1)
|
||||
enqueue(:refresh_subscriptions, nil)
|
||||
end)
|
||||
GenServer.start_link(__MODULE__, %{
|
||||
in: {:sets.new(), []},
|
||||
out: {:sets.new(), []}
|
||||
}, name: __MODULE__)
|
||||
|
||||
GenServer.start_link(
|
||||
__MODULE__,
|
||||
%{
|
||||
in: {:sets.new(), []},
|
||||
out: {:sets.new(), []}
|
||||
},
|
||||
name: __MODULE__
|
||||
)
|
||||
end
|
||||
|
||||
def handle(:refresh_subscriptions, _) do
|
||||
Logger.debug("Federator running refresh subscriptions")
|
||||
Websub.refresh_subscriptions()
|
||||
|
||||
spawn(fn ->
|
||||
Process.sleep(1000 * 60 * 60 * 6) # 6 hours
|
||||
# 6 hours
|
||||
Process.sleep(1000 * 60 * 60 * 6)
|
||||
enqueue(:refresh_subscriptions, nil)
|
||||
end)
|
||||
end
|
||||
|
||||
def handle(:request_subscription, websub) do
|
||||
Logger.debug("Refreshing #{websub.topic}")
|
||||
with {:ok, websub } <- Websub.request_subscription(websub) do
|
||||
|
||||
with {:ok, websub} <- Websub.request_subscription(websub) do
|
||||
Logger.debug("Successfully refreshed #{websub.topic}")
|
||||
else
|
||||
_e -> Logger.debug("Couldn't refresh #{websub.topic}")
|
||||
|
@ -45,8 +54,10 @@ def handle(:request_subscription, websub) do
|
|||
|
||||
def handle(:publish, activity) do
|
||||
Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end)
|
||||
|
||||
with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||
{:ok, actor} = WebFinger.ensure_keys_present(actor)
|
||||
|
||||
if ActivityPub.is_public?(activity) do
|
||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end)
|
||||
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
|
||||
|
@ -61,7 +72,10 @@ def handle(:publish, activity) do
|
|||
end
|
||||
|
||||
def handle(:verify_websub, websub) do
|
||||
Logger.debug(fn -> "Running WebSub verification for #{websub.id} (#{websub.topic}, #{websub.callback})" end)
|
||||
Logger.debug(fn ->
|
||||
"Running WebSub verification for #{websub.id} (#{websub.topic}, #{websub.callback})"
|
||||
end)
|
||||
|
||||
@websub.verify(websub)
|
||||
end
|
||||
|
||||
|
@ -72,16 +86,18 @@ def handle(:incoming_doc, doc) do
|
|||
|
||||
def handle(:incoming_ap_doc, params) do
|
||||
Logger.info("Handling incoming AP activity")
|
||||
|
||||
with {:ok, _user} <- ap_enabled_actor(params["actor"]),
|
||||
nil <- Activity.get_by_ap_id(params["id"]),
|
||||
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
|
||||
else
|
||||
%Activity{} ->
|
||||
Logger.info("Already had #{params["id"]}")
|
||||
|
||||
e ->
|
||||
# Just drop those for now
|
||||
Logger.info("Unhandled activity")
|
||||
Logger.info(Poison.encode!(params, [pretty: 2]))
|
||||
Logger.info(Poison.encode!(params, pretty: 2))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -93,12 +109,21 @@ def handle(:publish_single_websub, %{xml: xml, topic: topic, callback: callback,
|
|||
signature = @websub.sign(secret || "", xml)
|
||||
Logger.debug(fn -> "Pushing #{topic} to #{callback}" end)
|
||||
|
||||
with {:ok, %{status_code: code}} <- @httpoison.post(callback, xml, [
|
||||
{"Content-Type", "application/atom+xml"},
|
||||
{"X-Hub-Signature", "sha1=#{signature}"}
|
||||
], timeout: 10000, recv_timeout: 20000, hackney: [pool: :default]) do
|
||||
with {:ok, %{status_code: code}} <-
|
||||
@httpoison.post(
|
||||
callback,
|
||||
xml,
|
||||
[
|
||||
{"Content-Type", "application/atom+xml"},
|
||||
{"X-Hub-Signature", "sha1=#{signature}"}
|
||||
],
|
||||
timeout: 10000,
|
||||
recv_timeout: 20000,
|
||||
hackney: [pool: :default]
|
||||
) do
|
||||
Logger.debug(fn -> "Pushed to #{callback}, code #{code}" end)
|
||||
else e ->
|
||||
else
|
||||
e ->
|
||||
Logger.debug(fn -> "Couldn't push to #{callback}, #{inspect(e)}" end)
|
||||
end
|
||||
end
|
||||
|
@ -110,7 +135,7 @@ def handle(type, _) do
|
|||
|
||||
def enqueue(type, payload, priority \\ 1) do
|
||||
if @federating do
|
||||
if Mix.env == :test do
|
||||
if Mix.env() == :test do
|
||||
handle(type, payload)
|
||||
else
|
||||
GenServer.cast(__MODULE__, {:enqueue, type, payload, priority})
|
||||
|
@ -119,7 +144,7 @@ def enqueue(type, payload, priority \\ 1) do
|
|||
end
|
||||
|
||||
def maybe_start_job(running_jobs, queue) do
|
||||
if (:sets.size(running_jobs) < @max_jobs) && queue != [] do
|
||||
if :sets.size(running_jobs) < @max_jobs && queue != [] do
|
||||
{{type, payload}, queue} = queue_pop(queue)
|
||||
{:ok, pid} = Task.start(fn -> handle(type, payload) end)
|
||||
mref = Process.monitor(pid)
|
||||
|
@ -129,7 +154,8 @@ def maybe_start_job(running_jobs, queue) do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_cast({:enqueue, type, payload, priority}, state) when type in [:incoming_doc, :incoming_ap_doc] do
|
||||
def handle_cast({:enqueue, type, payload, priority}, state)
|
||||
when type in [:incoming_doc, :incoming_ap_doc] do
|
||||
%{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state
|
||||
i_queue = enqueue_sorted(i_queue, {type, payload}, 1)
|
||||
{i_running_jobs, i_queue} = maybe_start_job(i_running_jobs, i_queue)
|
||||
|
@ -160,7 +186,7 @@ def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do
|
|||
|
||||
def enqueue_sorted(queue, element, priority) do
|
||||
[%{item: element, priority: priority} | queue]
|
||||
|> Enum.sort_by(fn (%{priority: priority}) -> priority end)
|
||||
|> Enum.sort_by(fn %{priority: priority} -> priority end)
|
||||
end
|
||||
|
||||
def queue_pop([%{item: element} | queue]) do
|
||||
|
@ -169,6 +195,7 @@ def queue_pop([%{item: element} | queue]) do
|
|||
|
||||
def ap_enabled_actor(id) do
|
||||
user = User.get_by_ap_id(id)
|
||||
|
||||
if User.ap_enabled?(user) do
|
||||
{:ok, user}
|
||||
else
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -11,8 +11,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
require Logger
|
||||
|
||||
def create_app(conn, params) do
|
||||
with cs <- App.register_changeset(%App{}, params) |> IO.inspect,
|
||||
{:ok, app} <- Repo.insert(cs) |> IO.inspect do
|
||||
with cs <- App.register_changeset(%App{}, params) |> IO.inspect(),
|
||||
{:ok, app} <- Repo.insert(cs) |> IO.inspect() do
|
||||
res = %{
|
||||
id: app.id,
|
||||
client_id: app.client_id,
|
||||
|
@ -25,51 +25,57 @@ def create_app(conn, params) do
|
|||
|
||||
def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
||||
original_user = user
|
||||
params = if bio = params["note"] do
|
||||
Map.put(params, "bio", bio)
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
params = if name = params["display_name"] do
|
||||
Map.put(params, "name", name)
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
user = if avatar = params["avatar"] do
|
||||
with %Plug.Upload{} <- avatar,
|
||||
{:ok, object} <- ActivityPub.upload(avatar),
|
||||
change = Ecto.Changeset.change(user, %{avatar: object.data}),
|
||||
{:ok, user} = User.update_and_set_cache(change) do
|
||||
user
|
||||
params =
|
||||
if bio = params["note"] do
|
||||
Map.put(params, "bio", bio)
|
||||
else
|
||||
_e -> user
|
||||
params
|
||||
end
|
||||
else
|
||||
user
|
||||
end
|
||||
|
||||
user = if banner = params["header"] do
|
||||
with %Plug.Upload{} <- banner,
|
||||
{:ok, object} <- ActivityPub.upload(banner),
|
||||
new_info <- Map.put(user.info, "banner", object.data),
|
||||
change <- User.info_changeset(user, %{info: new_info}),
|
||||
{:ok, user} <- User.update_and_set_cache(change) do
|
||||
user
|
||||
params =
|
||||
if name = params["display_name"] do
|
||||
Map.put(params, "name", name)
|
||||
else
|
||||
_e -> user
|
||||
params
|
||||
end
|
||||
|
||||
user =
|
||||
if avatar = params["avatar"] do
|
||||
with %Plug.Upload{} <- avatar,
|
||||
{:ok, object} <- ActivityPub.upload(avatar),
|
||||
change = Ecto.Changeset.change(user, %{avatar: object.data}),
|
||||
{:ok, user} = User.update_and_set_cache(change) do
|
||||
user
|
||||
else
|
||||
_e -> user
|
||||
end
|
||||
else
|
||||
user
|
||||
end
|
||||
|
||||
user =
|
||||
if banner = params["header"] do
|
||||
with %Plug.Upload{} <- banner,
|
||||
{:ok, object} <- ActivityPub.upload(banner),
|
||||
new_info <- Map.put(user.info, "banner", object.data),
|
||||
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
|
||||
else
|
||||
user
|
||||
end
|
||||
|
||||
with changeset <- User.update_changeset(user, params),
|
||||
{:ok, user} <- User.update_and_set_cache(changeset) do
|
||||
if original_user != user do
|
||||
CommonAPI.update(user)
|
||||
end
|
||||
json conn, AccountView.render("account.json", %{user: user})
|
||||
|
||||
json(conn, AccountView.render("account.json", %{user: user}))
|
||||
else
|
||||
_e ->
|
||||
conn
|
||||
|
@ -88,9 +94,10 @@ def user(conn, %{"id" => id}) do
|
|||
account = AccountView.render("account.json", %{user: user})
|
||||
json(conn, account)
|
||||
else
|
||||
_e -> conn
|
||||
|> put_status(404)
|
||||
|> json(%{error: "Can't find user"})
|
||||
_e ->
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json(%{error: "Can't find user"})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -98,16 +105,16 @@ def user(conn, %{"id" => id}) do
|
|||
|
||||
def masto_instance(conn, _params) do
|
||||
response = %{
|
||||
uri: Web.base_url,
|
||||
uri: Web.base_url(),
|
||||
title: Keyword.get(@instance, :name),
|
||||
description: "A Pleroma instance, an alternative fediverse server",
|
||||
version: Keyword.get(@instance, :version),
|
||||
email: Keyword.get(@instance, :email),
|
||||
urls: %{
|
||||
streaming_api: String.replace(Web.base_url, ["http","https"], "wss")
|
||||
streaming_api: String.replace(Web.base_url(), ["http", "https"], "wss")
|
||||
},
|
||||
stats: Stats.get_stats,
|
||||
thumbnail: Web.base_url <> "/instance/thumbnail.jpeg",
|
||||
stats: Stats.get_stats(),
|
||||
thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg",
|
||||
max_toot_chars: Keyword.get(@instance, :limit)
|
||||
}
|
||||
|
||||
|
@ -115,13 +122,14 @@ def masto_instance(conn, _params) do
|
|||
end
|
||||
|
||||
def peers(conn, _params) do
|
||||
json(conn, Stats.get_peers)
|
||||
json(conn, Stats.get_peers())
|
||||
end
|
||||
|
||||
defp mastodonized_emoji do
|
||||
Pleroma.Formatter.get_custom_emoji()
|
||||
|> Enum.map(fn {shortcode, relative_url} ->
|
||||
url = to_string URI.merge(Web.base_url(), relative_url)
|
||||
url = to_string(URI.merge(Web.base_url(), relative_url))
|
||||
|
||||
%{
|
||||
"shortcode" => shortcode,
|
||||
"static_url" => url,
|
||||
|
@ -132,26 +140,30 @@ defp mastodonized_emoji do
|
|||
|
||||
def custom_emojis(conn, _params) do
|
||||
mastodon_emoji = mastodonized_emoji()
|
||||
json conn, mastodon_emoji
|
||||
json(conn, mastodon_emoji)
|
||||
end
|
||||
|
||||
defp add_link_headers(conn, method, activities, param \\ false) do
|
||||
last = List.last(activities)
|
||||
first = List.first(activities)
|
||||
|
||||
if last do
|
||||
min = last.id
|
||||
max = first.id
|
||||
{next_url, prev_url} = if param do
|
||||
{
|
||||
mastodon_api_url(Pleroma.Web.Endpoint, method, param, max_id: min),
|
||||
mastodon_api_url(Pleroma.Web.Endpoint, method, param, since_id: max)
|
||||
}
|
||||
else
|
||||
{
|
||||
mastodon_api_url(Pleroma.Web.Endpoint, method, max_id: min),
|
||||
mastodon_api_url(Pleroma.Web.Endpoint, method, since_id: max)
|
||||
}
|
||||
end
|
||||
|
||||
{next_url, prev_url} =
|
||||
if param do
|
||||
{
|
||||
mastodon_api_url(Pleroma.Web.Endpoint, method, param, max_id: min),
|
||||
mastodon_api_url(Pleroma.Web.Endpoint, method, param, since_id: max)
|
||||
}
|
||||
else
|
||||
{
|
||||
mastodon_api_url(Pleroma.Web.Endpoint, method, max_id: min),
|
||||
mastodon_api_url(Pleroma.Web.Endpoint, method, since_id: max)
|
||||
}
|
||||
end
|
||||
|
||||
conn
|
||||
|> put_resp_header("link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
|
||||
else
|
||||
|
@ -160,13 +172,15 @@ defp add_link_headers(conn, method, activities, param \\ false) do
|
|||
end
|
||||
|
||||
def home_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||
params = params
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("user", user)
|
||||
params =
|
||||
params
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("user", user)
|
||||
|
||||
activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||
|> Enum.reverse
|
||||
activities =
|
||||
ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
|> add_link_headers(:home_timeline, activities)
|
||||
|
@ -174,13 +188,15 @@ def home_timeline(%{assigns: %{user: user}} = conn, params) do
|
|||
end
|
||||
|
||||
def public_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||
params = params
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("local_only", params["local"] in [true, "True", "true", "1"])
|
||||
|> Map.put("blocking_user", user)
|
||||
params =
|
||||
params
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("local_only", params["local"] in [true, "True", "true", "1"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|
||||
activities = ActivityPub.fetch_public_activities(params)
|
||||
|> Enum.reverse
|
||||
activities =
|
||||
ActivityPub.fetch_public_activities(params)
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
|> add_link_headers(:public_timeline, activities)
|
||||
|
@ -189,13 +205,15 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do
|
|||
|
||||
def user_statuses(%{assigns: %{user: user}} = conn, params) do
|
||||
with %User{ap_id: ap_id} <- Repo.get(User, params["id"]) do
|
||||
params = params
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("actor_id", ap_id)
|
||||
|> Map.put("whole_db", true)
|
||||
params =
|
||||
params
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("actor_id", ap_id)
|
||||
|> Map.put("whole_db", true)
|
||||
|
||||
activities = ActivityPub.fetch_public_activities(params)
|
||||
|> Enum.reverse
|
||||
activities =
|
||||
ActivityPub.fetch_public_activities(params)
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
|> add_link_headers(:user_statuses, activities, params["id"])
|
||||
|
@ -206,19 +224,39 @@ def user_statuses(%{assigns: %{user: user}} = conn, params) do
|
|||
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||
true <- ActivityPub.visible_for_user?(activity, user) do
|
||||
render conn, StatusView, "status.json", %{activity: activity, for: user}
|
||||
render(conn, StatusView, "status.json", %{activity: activity, for: user})
|
||||
end
|
||||
end
|
||||
|
||||
def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||
activities <- ActivityPub.fetch_activities_for_context(activity.data["context"], %{"blocking_user" => user, "user" => user}),
|
||||
activities <- activities |> Enum.filter(fn (%{id: aid}) -> to_string(aid) != to_string(id) end),
|
||||
activities <- activities |> Enum.filter(fn (%{data: %{"type" => type}}) -> type == "Create" end),
|
||||
grouped_activities <- Enum.group_by(activities, fn (%{id: id}) -> id < activity.id end) do
|
||||
activities <-
|
||||
ActivityPub.fetch_activities_for_context(activity.data["context"], %{
|
||||
"blocking_user" => user,
|
||||
"user" => user
|
||||
}),
|
||||
activities <-
|
||||
activities |> Enum.filter(fn %{id: aid} -> to_string(aid) != to_string(id) end),
|
||||
activities <-
|
||||
activities |> Enum.filter(fn %{data: %{"type" => type}} -> type == "Create" end),
|
||||
grouped_activities <- Enum.group_by(activities, fn %{id: id} -> id < activity.id end) do
|
||||
result = %{
|
||||
ancestors: StatusView.render("index.json", for: user, activities: grouped_activities[true] || [], as: :activity) |> Enum.reverse,
|
||||
descendants: StatusView.render("index.json", for: user, activities: grouped_activities[false] || [], as: :activity) |> Enum.reverse,
|
||||
ancestors:
|
||||
StatusView.render(
|
||||
"index.json",
|
||||
for: user,
|
||||
activities: grouped_activities[true] || [],
|
||||
as: :activity
|
||||
)
|
||||
|> Enum.reverse(),
|
||||
descendants:
|
||||
StatusView.render(
|
||||
"index.json",
|
||||
for: user,
|
||||
activities: grouped_activities[false] || [],
|
||||
as: :activity
|
||||
)
|
||||
|> Enum.reverse()
|
||||
}
|
||||
|
||||
json(conn, result)
|
||||
|
@ -226,12 +264,13 @@ def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|||
end
|
||||
|
||||
def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
|
||||
params = params
|
||||
|> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
|
||||
|> Map.put("no_attachment_links", true)
|
||||
params =
|
||||
params
|
||||
|> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
|
||||
|> Map.put("no_attachment_links", true)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, params)
|
||||
render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}
|
||||
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
end
|
||||
|
||||
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
|
@ -247,30 +286,32 @@ def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|||
|
||||
def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||
with {:ok, announce, _activity} = CommonAPI.repeat(ap_id_or_id, user) do
|
||||
render conn, StatusView, "status.json", %{activity: announce, for: user, as: :activity}
|
||||
render(conn, StatusView, "status.json", %{activity: announce, for: user, as: :activity})
|
||||
end
|
||||
end
|
||||
|
||||
def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||
with {:ok, _fav, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user),
|
||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||
render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}
|
||||
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
end
|
||||
end
|
||||
|
||||
def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||
with {:ok, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user),
|
||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||
render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}
|
||||
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
end
|
||||
end
|
||||
|
||||
def notifications(%{assigns: %{user: user}} = conn, params) do
|
||||
notifications = Notification.for_user(user, params)
|
||||
result = Enum.map(notifications, fn x ->
|
||||
render_notification(user, x)
|
||||
end)
|
||||
|> Enum.filter(&(&1))
|
||||
|
||||
result =
|
||||
Enum.map(notifications, fn x ->
|
||||
render_notification(user, x)
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
conn
|
||||
|> add_link_headers(:notifications, notifications)
|
||||
|
@ -306,27 +347,26 @@ def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _para
|
|||
|
||||
def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
id = List.wrap(id)
|
||||
q = from u in User,
|
||||
where: u.id in ^id
|
||||
q = from(u in User, where: u.id in ^id)
|
||||
targets = Repo.all(q)
|
||||
render conn, AccountView, "relationships.json", %{user: user, targets: targets}
|
||||
render(conn, AccountView, "relationships.json", %{user: user, targets: targets})
|
||||
end
|
||||
|
||||
def upload(%{assigns: %{user: _}} = conn, %{"file" => file}) do
|
||||
with {:ok, object} <- ActivityPub.upload(file) do
|
||||
data = object.data
|
||||
|> Map.put("id", object.id)
|
||||
data =
|
||||
object.data
|
||||
|> Map.put("id", object.id)
|
||||
|
||||
render conn, StatusView, "attachment.json", %{attachment: data}
|
||||
render(conn, StatusView, "attachment.json", %{attachment: data})
|
||||
end
|
||||
end
|
||||
|
||||
def favourited_by(conn, %{"id" => 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)
|
||||
users = Repo.all(q)
|
||||
render conn, AccountView, "accounts.json", %{users: users, as: :user}
|
||||
render(conn, AccountView, "accounts.json", %{users: users, as: :user})
|
||||
else
|
||||
_ -> json(conn, [])
|
||||
end
|
||||
|
@ -334,23 +374,24 @@ def favourited_by(conn, %{"id" => id}) do
|
|||
|
||||
def reblogged_by(conn, %{"id" => id}) do
|
||||
with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Repo.get(Activity, id) do
|
||||
q = from u in User,
|
||||
where: u.ap_id in ^announces
|
||||
q = from(u in User, where: u.ap_id in ^announces)
|
||||
users = Repo.all(q)
|
||||
render conn, AccountView, "accounts.json", %{users: users, as: :user}
|
||||
render(conn, AccountView, "accounts.json", %{users: users, as: :user})
|
||||
else
|
||||
_ -> json(conn, [])
|
||||
end
|
||||
end
|
||||
|
||||
def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||
params = params
|
||||
|> Map.put("type", "Create")
|
||||
|> Map.put("local_only", !!params["local"])
|
||||
|> Map.put("blocking_user", user)
|
||||
params =
|
||||
params
|
||||
|> Map.put("type", "Create")
|
||||
|> Map.put("local_only", !!params["local"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|
||||
activities = ActivityPub.fetch_public_activities(params)
|
||||
|> Enum.reverse
|
||||
activities =
|
||||
ActivityPub.fetch_public_activities(params)
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
|> add_link_headers(:hashtag_timeline, activities, params["tag"])
|
||||
|
@ -361,14 +402,14 @@ def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
|
|||
def followers(conn, %{"id" => id}) do
|
||||
with %User{} = user <- Repo.get(User, id),
|
||||
{:ok, followers} <- User.get_followers(user) do
|
||||
render conn, AccountView, "accounts.json", %{users: followers, as: :user}
|
||||
render(conn, AccountView, "accounts.json", %{users: followers, as: :user})
|
||||
end
|
||||
end
|
||||
|
||||
def following(conn, %{"id" => id}) do
|
||||
with %User{} = user <- Repo.get(User, id),
|
||||
{:ok, followers} <- User.get_friends(user) do
|
||||
render conn, AccountView, "accounts.json", %{users: followers, as: :user}
|
||||
render(conn, AccountView, "accounts.json", %{users: followers, as: :user})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -376,7 +417,7 @@ def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
|||
with %User{} = followed <- Repo.get(User, id),
|
||||
{:ok, follower} <- User.follow(follower, followed),
|
||||
{:ok, _activity} <- ActivityPub.follow(follower, followed) do
|
||||
render conn, AccountView, "relationship.json", %{user: follower, target: followed}
|
||||
render(conn, AccountView, "relationship.json", %{user: follower, target: followed})
|
||||
else
|
||||
{:error, message} ->
|
||||
conn
|
||||
|
@ -389,7 +430,7 @@ def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
|
|||
with %User{} = followed <- Repo.get_by(User, nickname: uri),
|
||||
{:ok, follower} <- User.follow(follower, followed),
|
||||
{:ok, _activity} <- ActivityPub.follow(follower, followed) do
|
||||
render conn, AccountView, "account.json", %{user: followed}
|
||||
render(conn, AccountView, "account.json", %{user: followed})
|
||||
else
|
||||
{:error, message} ->
|
||||
conn
|
||||
|
@ -401,20 +442,22 @@ def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
|
|||
# TODO: Clean up and unify
|
||||
def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||
with %User{} = followed <- Repo.get(User, id),
|
||||
{ :ok, follower, follow_activity } <- User.unfollow(follower, followed),
|
||||
{ :ok, _activity } <- ActivityPub.insert(%{
|
||||
"type" => "Undo",
|
||||
"actor" => follower.ap_id,
|
||||
"object" => follow_activity.data["id"] # get latest Follow for these users
|
||||
}) do
|
||||
render conn, AccountView, "relationship.json", %{user: follower, target: followed}
|
||||
{:ok, follower, follow_activity} <- User.unfollow(follower, followed),
|
||||
{:ok, _activity} <-
|
||||
ActivityPub.insert(%{
|
||||
"type" => "Undo",
|
||||
"actor" => follower.ap_id,
|
||||
# get latest Follow for these users
|
||||
"object" => follow_activity.data["id"]
|
||||
}) do
|
||||
render(conn, AccountView, "relationship.json", %{user: follower, target: followed})
|
||||
end
|
||||
end
|
||||
|
||||
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||
with %User{} = blocked <- Repo.get(User, id),
|
||||
{:ok, blocker} <- User.block(blocker, blocked) do
|
||||
render conn, AccountView, "relationship.json", %{user: blocker, target: blocked}
|
||||
render(conn, AccountView, "relationship.json", %{user: blocker, target: blocked})
|
||||
else
|
||||
{:error, message} ->
|
||||
conn
|
||||
|
@ -426,7 +469,7 @@ def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
|||
def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||
with %User{} = blocked <- Repo.get(User, id),
|
||||
{:ok, blocker} <- User.unblock(blocker, blocked) do
|
||||
render conn, AccountView, "relationship.json", %{user: blocker, target: blocked}
|
||||
render(conn, AccountView, "relationship.json", %{user: blocker, target: blocked})
|
||||
else
|
||||
{:error, message} ->
|
||||
conn
|
||||
|
@ -438,7 +481,7 @@ def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
|||
# TODO: Use proper query
|
||||
def blocks(%{assigns: %{user: user}} = conn, _) do
|
||||
with blocked_users <- user.info["blocks"] || [],
|
||||
accounts <- Enum.map(blocked_users, fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end) do
|
||||
accounts <- Enum.map(blocked_users, fn ap_id -> User.get_cached_by_ap_id(ap_id) end) do
|
||||
res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
|
||||
json(conn, res)
|
||||
end
|
||||
|
@ -447,23 +490,34 @@ def blocks(%{assigns: %{user: user}} = conn, _) do
|
|||
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
accounts = User.search(query, params["resolve"] == "true")
|
||||
|
||||
fetched = if Regex.match?(~r/https?:/, query) do
|
||||
with {:ok, activities} <- OStatus.fetch_activity_from_url(query) do
|
||||
activities
|
||||
else
|
||||
_e -> []
|
||||
end
|
||||
end || []
|
||||
fetched =
|
||||
if Regex.match?(~r/https?:/, query) do
|
||||
with {:ok, activities} <- OStatus.fetch_activity_from_url(query) do
|
||||
activities
|
||||
else
|
||||
_e -> []
|
||||
end
|
||||
end || []
|
||||
|
||||
q =
|
||||
from(
|
||||
a in Activity,
|
||||
where: fragment("?->>'type' = 'Create'", a.data),
|
||||
where:
|
||||
fragment(
|
||||
"to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)",
|
||||
a.data,
|
||||
^query
|
||||
),
|
||||
limit: 20
|
||||
)
|
||||
|
||||
q = from a in Activity,
|
||||
where: fragment("?->>'type' = 'Create'", a.data),
|
||||
where: fragment("to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)", a.data, ^query),
|
||||
limit: 20
|
||||
statuses = Repo.all(q) ++ fetched
|
||||
|
||||
res = %{
|
||||
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
|
||||
"statuses" => StatusView.render("index.json", activities: statuses, for: user, as: :activity),
|
||||
"statuses" =>
|
||||
StatusView.render("index.json", activities: statuses, for: user, as: :activity),
|
||||
"hashtags" => []
|
||||
}
|
||||
|
||||
|
@ -479,94 +533,102 @@ def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) d
|
|||
end
|
||||
|
||||
def favourites(%{assigns: %{user: user}} = conn, _) do
|
||||
params = %{}
|
||||
|> Map.put("type", "Create")
|
||||
|> Map.put("favorited_by", user.ap_id)
|
||||
|> Map.put("blocking_user", user)
|
||||
params =
|
||||
%{}
|
||||
|> Map.put("type", "Create")
|
||||
|> Map.put("favorited_by", user.ap_id)
|
||||
|> Map.put("blocking_user", user)
|
||||
|
||||
activities = ActivityPub.fetch_public_activities(params)
|
||||
|> Enum.reverse
|
||||
activities =
|
||||
ActivityPub.fetch_public_activities(params)
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
|> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
|
||||
end
|
||||
|
||||
def index(%{assigns: %{user: user}} = conn, _params) do
|
||||
token = conn
|
||||
|> get_session(:oauth_token)
|
||||
token =
|
||||
conn
|
||||
|> get_session(:oauth_token)
|
||||
|
||||
if user && token do
|
||||
mastodon_emoji = mastodonized_emoji()
|
||||
accounts = Map.put(%{}, user.id, AccountView.render("account.json", %{user: user}))
|
||||
initial_state = %{
|
||||
meta: %{
|
||||
streaming_api_base_url: String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
|
||||
access_token: token,
|
||||
locale: "en",
|
||||
domain: Pleroma.Web.Endpoint.host(),
|
||||
admin: "1",
|
||||
me: "#{user.id}",
|
||||
unfollow_modal: false,
|
||||
boost_modal: false,
|
||||
delete_modal: true,
|
||||
auto_play_gif: false,
|
||||
reduce_motion: false
|
||||
},
|
||||
compose: %{
|
||||
me: "#{user.id}",
|
||||
default_privacy: "public",
|
||||
default_sensitive: false
|
||||
},
|
||||
media_attachments: %{
|
||||
accept_content_types: [
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".png",
|
||||
".gif",
|
||||
".webm",
|
||||
".mp4",
|
||||
".m4v",
|
||||
"image\/jpeg",
|
||||
"image\/png",
|
||||
"image\/gif",
|
||||
"video\/webm",
|
||||
"video\/mp4"
|
||||
]
|
||||
},
|
||||
settings: %{
|
||||
onboarded: true,
|
||||
home: %{
|
||||
shows: %{
|
||||
reblog: true,
|
||||
reply: true
|
||||
|
||||
initial_state =
|
||||
%{
|
||||
meta: %{
|
||||
streaming_api_base_url:
|
||||
String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
|
||||
access_token: token,
|
||||
locale: "en",
|
||||
domain: Pleroma.Web.Endpoint.host(),
|
||||
admin: "1",
|
||||
me: "#{user.id}",
|
||||
unfollow_modal: false,
|
||||
boost_modal: false,
|
||||
delete_modal: true,
|
||||
auto_play_gif: false,
|
||||
reduce_motion: false
|
||||
},
|
||||
compose: %{
|
||||
me: "#{user.id}",
|
||||
default_privacy: "public",
|
||||
default_sensitive: false
|
||||
},
|
||||
media_attachments: %{
|
||||
accept_content_types: [
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".png",
|
||||
".gif",
|
||||
".webm",
|
||||
".mp4",
|
||||
".m4v",
|
||||
"image\/jpeg",
|
||||
"image\/png",
|
||||
"image\/gif",
|
||||
"video\/webm",
|
||||
"video\/mp4"
|
||||
]
|
||||
},
|
||||
settings: %{
|
||||
onboarded: true,
|
||||
home: %{
|
||||
shows: %{
|
||||
reblog: true,
|
||||
reply: true
|
||||
}
|
||||
},
|
||||
notifications: %{
|
||||
alerts: %{
|
||||
follow: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
mention: true
|
||||
},
|
||||
shows: %{
|
||||
follow: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
mention: true
|
||||
},
|
||||
sounds: %{
|
||||
follow: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
mention: true
|
||||
}
|
||||
}
|
||||
},
|
||||
notifications: %{
|
||||
alerts: %{
|
||||
follow: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
mention: true
|
||||
},
|
||||
shows: %{
|
||||
follow: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
mention: true
|
||||
},
|
||||
sounds: %{
|
||||
follow: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
mention: true
|
||||
}
|
||||
}
|
||||
},
|
||||
push_subscription: nil,
|
||||
accounts: accounts,
|
||||
custom_emojis: mastodon_emoji,
|
||||
char_limit: Keyword.get(@instance, :limit)
|
||||
} |> Jason.encode!
|
||||
push_subscription: nil,
|
||||
accounts: accounts,
|
||||
custom_emojis: mastodon_emoji,
|
||||
char_limit: Keyword.get(@instance, :limit)
|
||||
}
|
||||
|> Jason.encode!()
|
||||
|
||||
conn
|
||||
|> put_layout(false)
|
||||
|> render(MastodonView, "index.html", %{initial_state: initial_state})
|
||||
|
@ -586,12 +648,18 @@ defp get_or_make_app() do
|
|||
{:ok, app}
|
||||
else
|
||||
_e ->
|
||||
cs = App.register_changeset(%App{}, %{client_name: "Mastodon-Local", redirect_uris: ".", scopes: "read,write,follow"})
|
||||
cs =
|
||||
App.register_changeset(%App{}, %{
|
||||
client_name: "Mastodon-Local",
|
||||
redirect_uris: ".",
|
||||
scopes: "read,write,follow"
|
||||
})
|
||||
|
||||
Repo.insert(cs)
|
||||
end
|
||||
end
|
||||
|
||||
def login_post(conn, %{"authorization" => %{ "name" => name, "password" => password}}) do
|
||||
def login_post(conn, %{"authorization" => %{"name" => name, "password" => password}}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(name),
|
||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||
{:ok, app} <- get_or_make_app(),
|
||||
|
@ -615,8 +683,9 @@ def logout(conn, _) do
|
|||
|
||||
def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
Logger.debug("Unimplemented, returning unmodified relationship")
|
||||
|
||||
with %User{} = target <- Repo.get(User, id) do
|
||||
render conn, AccountView, "relationship.json", %{user: user, target: target}
|
||||
render(conn, AccountView, "relationship.json", %{user: user, target: target})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -632,20 +701,53 @@ def empty_object(conn, _) do
|
|||
|
||||
def render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do
|
||||
actor = User.get_cached_by_ap_id(activity.data["actor"])
|
||||
created_at = NaiveDateTime.to_iso8601(created_at)
|
||||
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
|
||||
|
||||
created_at =
|
||||
NaiveDateTime.to_iso8601(created_at)
|
||||
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
|
||||
|
||||
case activity.data["type"] do
|
||||
"Create" ->
|
||||
%{id: id, type: "mention", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: activity, for: user})}
|
||||
%{
|
||||
id: id,
|
||||
type: "mention",
|
||||
created_at: created_at,
|
||||
account: AccountView.render("account.json", %{user: actor}),
|
||||
status: StatusView.render("status.json", %{activity: activity, for: user})
|
||||
}
|
||||
|
||||
"Like" ->
|
||||
liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
|
||||
%{id: id, type: "favourite", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: liked_activity, for: user})}
|
||||
|
||||
%{
|
||||
id: id,
|
||||
type: "favourite",
|
||||
created_at: created_at,
|
||||
account: AccountView.render("account.json", %{user: actor}),
|
||||
status: StatusView.render("status.json", %{activity: liked_activity, for: user})
|
||||
}
|
||||
|
||||
"Announce" ->
|
||||
announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
|
||||
%{id: id, type: "reblog", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: announced_activity, for: user})}
|
||||
|
||||
%{
|
||||
id: id,
|
||||
type: "reblog",
|
||||
created_at: created_at,
|
||||
account: AccountView.render("account.json", %{user: actor}),
|
||||
status: StatusView.render("status.json", %{activity: announced_activity, for: user})
|
||||
}
|
||||
|
||||
"Follow" ->
|
||||
%{id: id, type: "follow", created_at: created_at, account: AccountView.render("account.json", %{user: actor})}
|
||||
_ -> nil
|
||||
%{
|
||||
id: id,
|
||||
type: "follow",
|
||||
created_at: created_at,
|
||||
account: AccountView.render("account.json", %{user: actor})
|
||||
}
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,17 +4,23 @@ defmodule Pleroma.Web.MastodonAPI.MastodonSocket do
|
|||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.{User, Repo}
|
||||
|
||||
transport :streaming, Phoenix.Transports.WebSocket.Raw,
|
||||
timeout: :infinity # We never receive data.
|
||||
transport(
|
||||
:streaming,
|
||||
Phoenix.Transports.WebSocket.Raw,
|
||||
# We never receive data.
|
||||
timeout: :infinity
|
||||
)
|
||||
|
||||
def connect(params, socket) do
|
||||
with token when not is_nil(token) <- params["access_token"],
|
||||
%Token{user_id: user_id} <- Repo.get_by(Token, token: token),
|
||||
%User{} = user <- Repo.get(User, user_id),
|
||||
stream when stream in ["public", "public:local", "user"] <- params["stream"] do
|
||||
socket = socket
|
||||
|> assign(:topic, params["stream"])
|
||||
|> assign(:user, user)
|
||||
socket =
|
||||
socket
|
||||
|> assign(:topic, params["stream"])
|
||||
|> assign(:user, user)
|
||||
|
||||
Pleroma.Web.Streamer.add_socket(params["stream"], socket)
|
||||
{:ok, socket}
|
||||
else
|
||||
|
@ -25,11 +31,11 @@ def connect(params, socket) do
|
|||
def id(_), do: nil
|
||||
|
||||
def handle(:text, message, _state) do
|
||||
#| :ok
|
||||
#| state
|
||||
#| {:text, message}
|
||||
#| {:text, message, state}
|
||||
#| {:close, "Goodbye!"}
|
||||
# | :ok
|
||||
# | state
|
||||
# | {:text, message}
|
||||
# | {:text, message, state}
|
||||
# | {:close, "Goodbye!"}
|
||||
{:text, message}
|
||||
end
|
||||
|
||||
|
|
|
@ -10,37 +10,52 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
defp get_replied_to_activities(activities) do
|
||||
activities
|
||||
|> Enum.map(fn
|
||||
(%{data: %{"type" => "Create", "object" => %{"inReplyTo" => inReplyTo}}}) ->
|
||||
(inReplyTo != "") && inReplyTo
|
||||
_ -> nil
|
||||
%{data: %{"type" => "Create", "object" => %{"inReplyTo" => inReplyTo}}} ->
|
||||
inReplyTo != "" && inReplyTo
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end)
|
||||
|> Enum.filter(&(&1))
|
||||
|> Enum.filter(& &1)
|
||||
|> Activity.create_activity_by_object_id_query()
|
||||
|> Repo.all
|
||||
|> Enum.reduce(%{}, fn(activity, acc) -> Map.put(acc,activity.data["object"]["id"], activity) end)
|
||||
|> Repo.all()
|
||||
|> Enum.reduce(%{}, fn activity, acc ->
|
||||
Map.put(acc, activity.data["object"]["id"], activity)
|
||||
end)
|
||||
end
|
||||
|
||||
def render("index.json", opts) do
|
||||
replied_to_activities = get_replied_to_activities(opts.activities)
|
||||
render_many(opts.activities, StatusView, "status.json", Map.put(opts, :replied_to_activities, replied_to_activities))
|
||||
|
||||
render_many(
|
||||
opts.activities,
|
||||
StatusView,
|
||||
"status.json",
|
||||
Map.put(opts, :replied_to_activities, replied_to_activities)
|
||||
)
|
||||
end
|
||||
|
||||
def render("status.json", %{activity: %{data: %{"type" => "Announce", "object" => object}} = activity} = opts) do
|
||||
def render(
|
||||
"status.json",
|
||||
%{activity: %{data: %{"type" => "Announce", "object" => object}} = activity} = opts
|
||||
) do
|
||||
user = User.get_cached_by_ap_id(activity.data["actor"])
|
||||
created_at = Utils.to_masto_date(activity.data["published"])
|
||||
|
||||
reblogged = Activity.get_create_activity_by_object_ap_id(object)
|
||||
reblogged = render("status.json", Map.put(opts, :activity, reblogged))
|
||||
|
||||
mentions = activity.recipients
|
||||
|> Enum.map(fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end)
|
||||
|> Enum.filter(&(&1))
|
||||
|> Enum.map(fn (user) -> AccountView.render("mention.json", %{user: user}) end)
|
||||
mentions =
|
||||
activity.recipients
|
||||
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
|
||||
|
||||
%{
|
||||
id: to_string(activity.id),
|
||||
uri: object,
|
||||
url: nil, # TODO: This might be wrong, check with mastodon.
|
||||
# TODO: This might be wrong, check with mastodon.
|
||||
url: nil,
|
||||
account: AccountView.render("account.json", %{user: user}),
|
||||
in_reply_to_id: nil,
|
||||
in_reply_to_account_id: nil,
|
||||
|
@ -89,27 +104,30 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
|||
tags = object["tag"] || []
|
||||
sensitive = object["sensitive"] || Enum.member?(tags, "nsfw")
|
||||
|
||||
mentions = activity.recipients
|
||||
|> Enum.map(fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end)
|
||||
|> Enum.filter(&(&1))
|
||||
|> Enum.map(fn (user) -> AccountView.render("mention.json", %{user: user}) end)
|
||||
mentions =
|
||||
activity.recipients
|
||||
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
|
||||
|
||||
repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || [])
|
||||
favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
|
||||
|
||||
attachments = render_many(object["attachment"] || [], StatusView, "attachment.json", as: :attachment)
|
||||
attachments =
|
||||
render_many(object["attachment"] || [], StatusView, "attachment.json", as: :attachment)
|
||||
|
||||
created_at = Utils.to_masto_date(object["published"])
|
||||
|
||||
reply_to = get_reply_to(activity, opts)
|
||||
reply_to_user = reply_to && User.get_cached_by_ap_id(reply_to.data["actor"])
|
||||
|
||||
emojis = (activity.data["object"]["emoji"] || [])
|
||||
|> Enum.map(fn {name, url} ->
|
||||
name = HtmlSanitizeEx.strip_tags(name)
|
||||
url = HtmlSanitizeEx.strip_tags(url)
|
||||
%{ shortcode: name, url: url, static_url: url }
|
||||
end)
|
||||
emojis =
|
||||
(activity.data["object"]["emoji"] || [])
|
||||
|> Enum.map(fn {name, url} ->
|
||||
name = HtmlSanitizeEx.strip_tags(name)
|
||||
url = HtmlSanitizeEx.strip_tags(url)
|
||||
%{shortcode: name, url: url, static_url: url}
|
||||
end)
|
||||
|
||||
%{
|
||||
id: to_string(activity.id),
|
||||
|
@ -131,7 +149,8 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
|||
visibility: get_visibility(object),
|
||||
media_attachments: attachments |> Enum.take(4),
|
||||
mentions: mentions,
|
||||
tags: [], # fix,
|
||||
# fix,
|
||||
tags: [],
|
||||
application: %{
|
||||
name: "Web",
|
||||
website: nil
|
||||
|
@ -145,10 +164,11 @@ def get_visibility(object) do
|
|||
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||
to = object["to"] || []
|
||||
cc = object["cc"] || []
|
||||
|
||||
cond do
|
||||
public in to -> "public"
|
||||
public in cc -> "unlisted"
|
||||
Enum.any?(to, &(String.contains?(&1, "/followers"))) -> "private"
|
||||
Enum.any?(to, &String.contains?(&1, "/followers")) -> "private"
|
||||
true -> "direct"
|
||||
end
|
||||
end
|
||||
|
@ -156,14 +176,15 @@ def get_visibility(object) do
|
|||
def render("attachment.json", %{attachment: attachment}) do
|
||||
[%{"mediaType" => media_type, "href" => href} | _] = attachment["url"]
|
||||
|
||||
type = cond do
|
||||
String.contains?(media_type, "image") -> "image"
|
||||
String.contains?(media_type, "video") -> "video"
|
||||
String.contains?(media_type, "audio") -> "audio"
|
||||
true -> "unknown"
|
||||
end
|
||||
type =
|
||||
cond do
|
||||
String.contains?(media_type, "image") -> "image"
|
||||
String.contains?(media_type, "video") -> "video"
|
||||
String.contains?(media_type, "audio") -> "audio"
|
||||
true -> "unknown"
|
||||
end
|
||||
|
||||
<< hash_id::signed-32, _rest::binary >> = :crypto.hash(:md5, href)
|
||||
<<hash_id::signed-32, _rest::binary>> = :crypto.hash(:md5, href)
|
||||
|
||||
%{
|
||||
id: to_string(attachment["id"] || hash_id),
|
||||
|
|
|
@ -4,47 +4,59 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
|
|||
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
|
||||
@max_body_length 25 * 1048576
|
||||
@max_body_length 25 * 1_048_576
|
||||
|
||||
@cache_control %{
|
||||
default: "public, max-age=1209600",
|
||||
error: "public, must-revalidate, max-age=160",
|
||||
error: "public, must-revalidate, max-age=160"
|
||||
}
|
||||
|
||||
def remote(conn, %{"sig" => sig, "url" => url}) do
|
||||
config = Application.get_env(:pleroma, :media_proxy, [])
|
||||
with \
|
||||
true <- Keyword.get(config, :enabled, false),
|
||||
{:ok, url} <- Pleroma.Web.MediaProxy.decode_url(sig, url),
|
||||
{:ok, content_type, body} <- proxy_request(url)
|
||||
do
|
||||
|
||||
with true <- Keyword.get(config, :enabled, false),
|
||||
{:ok, url} <- Pleroma.Web.MediaProxy.decode_url(sig, url),
|
||||
{:ok, content_type, body} <- proxy_request(url) do
|
||||
conn
|
||||
|> put_resp_content_type(content_type)
|
||||
|> set_cache_header(:default)
|
||||
|> send_resp(200, body)
|
||||
else
|
||||
false -> send_error(conn, 404)
|
||||
{:error, :invalid_signature} -> send_error(conn, 403)
|
||||
{:error, {:http, _, url}} -> redirect_or_error(conn, url, Keyword.get(config, :redirect_on_failure, true))
|
||||
false ->
|
||||
send_error(conn, 404)
|
||||
|
||||
{:error, :invalid_signature} ->
|
||||
send_error(conn, 403)
|
||||
|
||||
{:error, {:http, _, url}} ->
|
||||
redirect_or_error(conn, url, Keyword.get(config, :redirect_on_failure, true))
|
||||
end
|
||||
end
|
||||
|
||||
defp proxy_request(link) do
|
||||
headers = [{"user-agent", "Pleroma/MediaProxy; #{Pleroma.Web.base_url()} <#{Application.get_env(:pleroma, :instance)[:email]}>"}]
|
||||
options = @httpoison.process_request_options([:insecure, {:follow_redirect, true}]) ++ [{:pool, :default}]
|
||||
with \
|
||||
{:ok, 200, headers, client} <- :hackney.request(:get, link, headers, "", options),
|
||||
headers = Enum.into(headers, Map.new),
|
||||
{:ok, body} <- proxy_request_body(client),
|
||||
content_type <- proxy_request_content_type(headers, body)
|
||||
do
|
||||
headers = [
|
||||
{"user-agent",
|
||||
"Pleroma/MediaProxy; #{Pleroma.Web.base_url()} <#{
|
||||
Application.get_env(:pleroma, :instance)[:email]
|
||||
}>"}
|
||||
]
|
||||
|
||||
options =
|
||||
@httpoison.process_request_options([:insecure, {:follow_redirect, true}]) ++
|
||||
[{:pool, :default}]
|
||||
|
||||
with {:ok, 200, headers, client} <- :hackney.request(:get, link, headers, "", options),
|
||||
headers = Enum.into(headers, Map.new()),
|
||||
{:ok, body} <- proxy_request_body(client),
|
||||
content_type <- proxy_request_content_type(headers, body) do
|
||||
{:ok, content_type, body}
|
||||
else
|
||||
{:ok, status, _, _} ->
|
||||
Logger.warn "MediaProxy: request failed, status #{status}, link: #{link}"
|
||||
Logger.warn("MediaProxy: request failed, status #{status}, link: #{link}")
|
||||
{:error, {:http, :bad_status, link}}
|
||||
|
||||
{:error, error} ->
|
||||
Logger.warn "MediaProxy: request failed, error #{inspect error}, link: #{link}"
|
||||
Logger.warn("MediaProxy: request failed, error #{inspect(error)}, link: #{link}")
|
||||
{:error, {:http, error, link}}
|
||||
end
|
||||
end
|
||||
|
@ -63,13 +75,15 @@ defp send_error(conn, code, body \\ "") do
|
|||
end
|
||||
|
||||
defp proxy_request_body(client), do: proxy_request_body(client, <<>>)
|
||||
|
||||
defp proxy_request_body(client, body) when byte_size(body) < @max_body_length do
|
||||
case :hackney.stream_body(client) do
|
||||
{:ok, data} -> proxy_request_body(client, <<body :: binary, data :: binary>>)
|
||||
{:ok, data} -> proxy_request_body(client, <<body::binary, data::binary>>)
|
||||
:done -> {:ok, body}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp proxy_request_body(client, _) do
|
||||
:hackney.close(client)
|
||||
{:error, :body_too_large}
|
||||
|
@ -80,5 +94,4 @@ defp proxy_request_body(client, _) do
|
|||
defp proxy_request_content_type(headers, _body) do
|
||||
headers["Content-Type"] || headers["content-type"] || "image/jpeg"
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -7,14 +7,15 @@ def url(url = "/" <> _), do: url
|
|||
|
||||
def url(url) do
|
||||
config = Application.get_env(:pleroma, :media_proxy, [])
|
||||
if !Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url) do
|
||||
|
||||
if !Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url()) do
|
||||
url
|
||||
else
|
||||
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
|
||||
base64 = Base.url_encode64(url, @base64_opts)
|
||||
sig = :crypto.hmac(:sha, secret, base64)
|
||||
sig64 = sig |> Base.url_encode64(@base64_opts)
|
||||
Keyword.get(config, :base_url, Pleroma.Web.base_url) <> "/proxy/#{sig64}/#{base64}"
|
||||
Keyword.get(config, :base_url, Pleroma.Web.base_url()) <> "/proxy/#{sig64}/#{base64}"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -22,11 +23,11 @@ def decode_url(sig, url) do
|
|||
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
|
||||
sig = Base.url_decode64!(sig, @base64_opts)
|
||||
local_sig = :crypto.hmac(:sha, secret, url)
|
||||
|
||||
if local_sig == sig do
|
||||
{:ok, Base.url_decode64!(url, @base64_opts)}
|
||||
else
|
||||
{:error, :invalid_signature}
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -3,25 +3,26 @@ defmodule Pleroma.Web.OAuth.App do
|
|||
import Ecto.{Changeset}
|
||||
|
||||
schema "apps" do
|
||||
field :client_name, :string
|
||||
field :redirect_uris, :string
|
||||
field :scopes, :string
|
||||
field :website, :string
|
||||
field :client_id, :string
|
||||
field :client_secret, :string
|
||||
field(:client_name, :string)
|
||||
field(:redirect_uris, :string)
|
||||
field(:scopes, :string)
|
||||
field(:website, :string)
|
||||
field(:client_id, :string)
|
||||
field(:client_secret, :string)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def register_changeset(struct, params \\ %{}) do
|
||||
changeset = struct
|
||||
|> cast(params, [:client_name, :redirect_uris, :scopes, :website])
|
||||
|> validate_required([:client_name, :redirect_uris, :scopes])
|
||||
changeset =
|
||||
struct
|
||||
|> cast(params, [:client_name, :redirect_uris, :scopes, :website])
|
||||
|> validate_required([:client_name, :redirect_uris, :scopes])
|
||||
|
||||
if changeset.valid? do
|
||||
changeset
|
||||
|> put_change(:client_id, :crypto.strong_rand_bytes(32) |> Base.url_encode64)
|
||||
|> put_change(:client_secret, :crypto.strong_rand_bytes(32) |> Base.url_encode64)
|
||||
|> put_change(:client_id, :crypto.strong_rand_bytes(32) |> Base.url_encode64())
|
||||
|> put_change(:client_secret, :crypto.strong_rand_bytes(32) |> Base.url_encode64())
|
||||
else
|
||||
changeset
|
||||
end
|
||||
|
|
|
@ -7,24 +7,24 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
|||
import Ecto.{Changeset}
|
||||
|
||||
schema "oauth_authorizations" do
|
||||
field :token, :string
|
||||
field :valid_until, :naive_datetime
|
||||
field :used, :boolean, default: false
|
||||
belongs_to :user, Pleroma.User
|
||||
belongs_to :app, Pleroma.App
|
||||
field(:token, :string)
|
||||
field(:valid_until, :naive_datetime)
|
||||
field(:used, :boolean, default: false)
|
||||
belongs_to(:user, Pleroma.User)
|
||||
belongs_to(:app, Pleroma.App)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def create_authorization(%App{} = app, %User{} = user) do
|
||||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64
|
||||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64()
|
||||
|
||||
authorization = %Authorization{
|
||||
token: token,
|
||||
used: false,
|
||||
user_id: user.id,
|
||||
app_id: app.id,
|
||||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now, 60 * 10)
|
||||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)
|
||||
}
|
||||
|
||||
Repo.insert(authorization)
|
||||
|
@ -37,11 +37,12 @@ def use_changeset(%Authorization{} = auth, params) do
|
|||
end
|
||||
|
||||
def use_token(%Authorization{used: false, valid_until: valid_until} = auth) do
|
||||
if NaiveDateTime.diff(NaiveDateTime.utc_now, valid_until) < 0 do
|
||||
if NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) < 0 do
|
||||
Repo.update(use_changeset(auth, %{used: true}))
|
||||
else
|
||||
{:error, "token expired"}
|
||||
end
|
||||
end
|
||||
|
||||
def use_token(%Authorization{used: true}), do: {:error, "already used"}
|
||||
end
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
defmodule Pleroma.Web.OAuth.FallbackController do
|
||||
use Pleroma.Web, :controller
|
||||
alias Pleroma.Web.OAuth.OAuthController
|
||||
|
||||
# No user/password
|
||||
def call(conn, _) do
|
||||
conn
|
||||
|> put_flash(:error, "Invalid Username/Password")
|
||||
|> OAuthController.authorize(conn.params)
|
||||
end
|
||||
use Pleroma.Web, :controller
|
||||
alias Pleroma.Web.OAuth.OAuthController
|
||||
|
||||
# No user/password
|
||||
def call(conn, _) do
|
||||
conn
|
||||
|> put_flash(:error, "Invalid Username/Password")
|
||||
|> OAuthController.authorize(conn.params)
|
||||
end
|
||||
end
|
|
@ -5,38 +5,49 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
alias Pleroma.{Repo, User}
|
||||
alias Comeonin.Pbkdf2
|
||||
|
||||
plug :fetch_session
|
||||
plug :fetch_flash
|
||||
plug(:fetch_session)
|
||||
plug(:fetch_flash)
|
||||
|
||||
action_fallback Pleroma.Web.OAuth.FallbackController
|
||||
action_fallback(Pleroma.Web.OAuth.FallbackController)
|
||||
|
||||
def authorize(conn, params) do
|
||||
render conn, "show.html", %{
|
||||
render(conn, "show.html", %{
|
||||
response_type: params["response_type"],
|
||||
client_id: params["client_id"],
|
||||
scope: params["scope"],
|
||||
redirect_uri: params["redirect_uri"],
|
||||
state: params["state"]
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
def create_authorization(conn, %{"authorization" => %{"name" => name, "password" => password, "client_id" => client_id, "redirect_uri" => redirect_uri} = params}) do
|
||||
def create_authorization(conn, %{
|
||||
"authorization" =>
|
||||
%{
|
||||
"name" => name,
|
||||
"password" => password,
|
||||
"client_id" => client_id,
|
||||
"redirect_uri" => redirect_uri
|
||||
} = params
|
||||
}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(name),
|
||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
||||
{:ok, auth} <- Authorization.create_authorization(app, user) do
|
||||
if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do
|
||||
render conn, "results.html", %{
|
||||
render(conn, "results.html", %{
|
||||
auth: auth
|
||||
}
|
||||
})
|
||||
else
|
||||
connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
|
||||
url = "#{redirect_uri}#{connector}code=#{auth.token}"
|
||||
url = if params["state"] do
|
||||
url <> "&state=#{params["state"]}"
|
||||
else
|
||||
url
|
||||
end
|
||||
|
||||
url =
|
||||
if params["state"] do
|
||||
url <> "&state=#{params["state"]}"
|
||||
else
|
||||
url
|
||||
end
|
||||
|
||||
redirect(conn, external: url)
|
||||
end
|
||||
end
|
||||
|
@ -45,7 +56,12 @@ def create_authorization(conn, %{"authorization" => %{"name" => name, "password"
|
|||
# TODO
|
||||
# - proper scope handling
|
||||
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
||||
with %App{} = app <- Repo.get_by(App, client_id: params["client_id"], client_secret: params["client_secret"]),
|
||||
with %App{} = app <-
|
||||
Repo.get_by(
|
||||
App,
|
||||
client_id: params["client_id"],
|
||||
client_secret: params["client_secret"]
|
||||
),
|
||||
fixed_token = fix_padding(params["code"]),
|
||||
%Authorization{} = auth <- Repo.get_by(Authorization, token: fixed_token, app_id: app.id),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
|
@ -56,6 +72,7 @@ def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
|||
expires_in: 60 * 10,
|
||||
scope: "read write follow"
|
||||
}
|
||||
|
||||
json(conn, response)
|
||||
else
|
||||
_error -> json(conn, %{error: "Invalid credentials"})
|
||||
|
@ -64,8 +81,16 @@ def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
|||
|
||||
# TODO
|
||||
# - investigate a way to verify the user wants to grant read/write/follow once scope handling is done
|
||||
def token_exchange(conn, %{"grant_type" => "password", "name" => name, "password" => password} = params) do
|
||||
with %App{} = app <- Repo.get_by(App, client_id: params["client_id"], client_secret: params["client_secret"]),
|
||||
def token_exchange(
|
||||
conn,
|
||||
%{"grant_type" => "password", "name" => name, "password" => password} = params
|
||||
) do
|
||||
with %App{} = app <-
|
||||
Repo.get_by(
|
||||
App,
|
||||
client_id: params["client_id"],
|
||||
client_secret: params["client_secret"]
|
||||
),
|
||||
%User{} = user <- User.get_cached_by_nickname(name),
|
||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||
{:ok, auth} <- Authorization.create_authorization(app, user),
|
||||
|
@ -77,6 +102,7 @@ def token_exchange(conn, %{"grant_type" => "password", "name" => name, "password
|
|||
expires_in: 60 * 10,
|
||||
scope: "read write follow"
|
||||
}
|
||||
|
||||
json(conn, response)
|
||||
else
|
||||
_error -> json(conn, %{error: "Invalid credentials"})
|
||||
|
@ -86,6 +112,6 @@ def token_exchange(conn, %{"grant_type" => "password", "name" => name, "password
|
|||
defp fix_padding(token) do
|
||||
token
|
||||
|> Base.url_decode64!(padding: false)
|
||||
|> Base.url_encode64
|
||||
|> Base.url_encode64()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,11 +5,11 @@ defmodule Pleroma.Web.OAuth.Token do
|
|||
alias Pleroma.Web.OAuth.{Token, App, Authorization}
|
||||
|
||||
schema "oauth_tokens" do
|
||||
field :token, :string
|
||||
field :refresh_token, :string
|
||||
field :valid_until, :naive_datetime
|
||||
belongs_to :user, Pleroma.User
|
||||
belongs_to :app, Pleroma.App
|
||||
field(:token, :string)
|
||||
field(:refresh_token, :string)
|
||||
field(:valid_until, :naive_datetime)
|
||||
belongs_to(:user, Pleroma.User)
|
||||
belongs_to(:app, Pleroma.App)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
@ -22,15 +22,15 @@ def exchange_token(app, auth) do
|
|||
end
|
||||
|
||||
def create_token(%App{} = app, %User{} = user) do
|
||||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64
|
||||
refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64
|
||||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64()
|
||||
refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64()
|
||||
|
||||
token = %Token{
|
||||
token: token,
|
||||
refresh_token: refresh_token,
|
||||
user_id: user.id,
|
||||
app_id: app.id,
|
||||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now, 60 * 10)
|
||||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)
|
||||
}
|
||||
|
||||
Repo.insert(token)
|
||||
|
|
|
@ -5,7 +5,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
|||
require Logger
|
||||
|
||||
defp get_href(id) do
|
||||
with %Object{data: %{ "external_url" => external_url } }<- Object.get_cached_by_ap_id(id) do
|
||||
with %Object{data: %{"external_url" => external_url}} <- Object.get_cached_by_ap_id(id) do
|
||||
external_url
|
||||
else
|
||||
_e -> id
|
||||
|
@ -13,42 +13,60 @@ defp get_href(id) do
|
|||
end
|
||||
|
||||
defp get_in_reply_to(%{"object" => %{"inReplyTo" => in_reply_to}}) do
|
||||
[{:"thr:in-reply-to", [ref: to_charlist(in_reply_to), href: to_charlist(get_href(in_reply_to))], []}]
|
||||
[
|
||||
{:"thr:in-reply-to",
|
||||
[ref: to_charlist(in_reply_to), href: to_charlist(get_href(in_reply_to))], []}
|
||||
]
|
||||
end
|
||||
|
||||
defp get_in_reply_to(_), do: []
|
||||
|
||||
defp get_mentions(to) do
|
||||
Enum.map(to, fn (id) ->
|
||||
Enum.map(to, fn id ->
|
||||
cond do
|
||||
# Special handling for the AP/Ostatus public collections
|
||||
"https://www.w3.org/ns/activitystreams#Public" == id ->
|
||||
{:link, [rel: "mentioned", "ostatus:object-type": "http://activitystrea.ms/schema/1.0/collection", href: "http://activityschema.org/collection/public"], []}
|
||||
{:link,
|
||||
[
|
||||
rel: "mentioned",
|
||||
"ostatus:object-type": "http://activitystrea.ms/schema/1.0/collection",
|
||||
href: "http://activityschema.org/collection/public"
|
||||
], []}
|
||||
|
||||
# Ostatus doesn't handle follower collections, ignore these.
|
||||
Regex.match?(~r/^#{Pleroma.Web.base_url}.+followers$/, id) ->
|
||||
Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) ->
|
||||
[]
|
||||
|
||||
true ->
|
||||
{:link, [rel: "mentioned", "ostatus:object-type": "http://activitystrea.ms/schema/1.0/person", href: id], []}
|
||||
{:link,
|
||||
[
|
||||
rel: "mentioned",
|
||||
"ostatus:object-type": "http://activitystrea.ms/schema/1.0/person",
|
||||
href: id
|
||||
], []}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp get_links(%{local: true, data: data}) do
|
||||
h = fn(str) -> [to_charlist(str)] end
|
||||
h = fn str -> [to_charlist(str)] end
|
||||
|
||||
[
|
||||
{:link, [type: ['application/atom+xml'], href: h.(data["object"]["id"]), rel: 'self'], []},
|
||||
{:link, [type: ['text/html'], href: h.(data["object"]["id"]), rel: 'alternate'], []}
|
||||
]
|
||||
end
|
||||
|
||||
defp get_links(%{local: false,
|
||||
data: %{
|
||||
"object" => %{
|
||||
"external_url" => external_url
|
||||
}
|
||||
}}) do
|
||||
defp get_links(%{
|
||||
local: false,
|
||||
data: %{
|
||||
"object" => %{
|
||||
"external_url" => external_url
|
||||
}
|
||||
}
|
||||
}) do
|
||||
h = fn str -> [to_charlist(str)] end
|
||||
|
||||
h = fn(str) -> [to_charlist(str)] end
|
||||
[
|
||||
{:link, [type: ['text/html'], href: h.(external_url), rel: 'alternate'], []}
|
||||
]
|
||||
|
@ -57,60 +75,72 @@ defp get_links(%{local: false,
|
|||
defp get_links(_activity), do: []
|
||||
|
||||
defp get_emoji_links(emojis) do
|
||||
Enum.map(emojis, fn({emoji, file}) ->
|
||||
Enum.map(emojis, fn {emoji, file} ->
|
||||
{:link, [name: to_charlist(emoji), rel: 'emoji', href: to_charlist(file)], []}
|
||||
end)
|
||||
end
|
||||
|
||||
def to_simple_form(activity, user, with_author \\ false)
|
||||
|
||||
def to_simple_form(%{data: %{"object" => %{"type" => "Note"}}} = activity, user, with_author) do
|
||||
h = fn(str) -> [to_charlist(str)] end
|
||||
h = fn str -> [to_charlist(str)] end
|
||||
|
||||
updated_at = activity.data["object"]["published"]
|
||||
inserted_at = activity.data["object"]["published"]
|
||||
|
||||
attachments = Enum.map(activity.data["object"]["attachment"] || [], fn(attachment) ->
|
||||
url = hd(attachment["url"])
|
||||
{:link, [rel: 'enclosure', href: to_charlist(url["href"]), type: to_charlist(url["mediaType"])], []}
|
||||
end)
|
||||
attachments =
|
||||
Enum.map(activity.data["object"]["attachment"] || [], fn attachment ->
|
||||
url = hd(attachment["url"])
|
||||
|
||||
{:link,
|
||||
[rel: 'enclosure', href: to_charlist(url["href"]), type: to_charlist(url["mediaType"])],
|
||||
[]}
|
||||
end)
|
||||
|
||||
in_reply_to = get_in_reply_to(activity.data)
|
||||
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
||||
mentions = activity.recipients |> get_mentions
|
||||
|
||||
categories = (activity.data["object"]["tag"] || [])
|
||||
|> Enum.map(fn (tag) ->
|
||||
if is_binary(tag) do
|
||||
{:category, [term: to_charlist(tag)], []}
|
||||
else
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(&(&1))
|
||||
categories =
|
||||
(activity.data["object"]["tag"] || [])
|
||||
|> Enum.map(fn tag ->
|
||||
if is_binary(tag) do
|
||||
{:category, [term: to_charlist(tag)], []}
|
||||
else
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
emoji_links = get_emoji_links(activity.data["object"]["emoji"] || %{})
|
||||
|
||||
summary = if activity.data["object"]["summary"] do
|
||||
[{:summary, [], h.(activity.data["object"]["summary"])}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
summary =
|
||||
if activity.data["object"]["summary"] do
|
||||
[{:summary, [], h.(activity.data["object"]["summary"])}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
[
|
||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/note']},
|
||||
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/post']},
|
||||
{:id, h.(activity.data["object"]["id"])}, # For notes, federate the object id.
|
||||
# For notes, federate the object id.
|
||||
{:id, h.(activity.data["object"]["id"])},
|
||||
{:title, ['New note by #{user.nickname}']},
|
||||
{:content, [type: 'html'], h.(activity.data["object"]["content"] |> String.replace(~r/[\n\r]/, ""))},
|
||||
{:content, [type: 'html'],
|
||||
h.(activity.data["object"]["content"] |> String.replace(~r/[\n\r]/, ""))},
|
||||
{:published, h.(inserted_at)},
|
||||
{:updated, h.(updated_at)},
|
||||
{:"ostatus:conversation", [ref: h.(activity.data["context"])], h.(activity.data["context"])},
|
||||
{:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []},
|
||||
] ++ summary ++ get_links(activity) ++ categories ++ attachments ++ in_reply_to ++ author ++ mentions ++ emoji_links
|
||||
{:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []}
|
||||
] ++
|
||||
summary ++
|
||||
get_links(activity) ++
|
||||
categories ++ attachments ++ in_reply_to ++ author ++ mentions ++ emoji_links
|
||||
end
|
||||
|
||||
def to_simple_form(%{data: %{"type" => "Like"}} = activity, user, with_author) do
|
||||
h = fn(str) -> [to_charlist(str)] end
|
||||
h = fn str -> [to_charlist(str)] end
|
||||
|
||||
updated_at = activity.data["published"]
|
||||
inserted_at = activity.data["published"]
|
||||
|
@ -126,10 +156,12 @@ def to_simple_form(%{data: %{"type" => "Like"}} = activity, user, with_author) d
|
|||
{:content, [type: 'html'], ['#{user.nickname} favorited something']},
|
||||
{:published, h.(inserted_at)},
|
||||
{:updated, h.(updated_at)},
|
||||
{:"activity:object", [
|
||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/note']},
|
||||
{:id, h.(activity.data["object"])}, # For notes, federate the object id.
|
||||
]},
|
||||
{:"activity:object",
|
||||
[
|
||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/note']},
|
||||
# For notes, federate the object id.
|
||||
{:id, h.(activity.data["object"])}
|
||||
]},
|
||||
{:"ostatus:conversation", [ref: h.(activity.data["context"])], h.(activity.data["context"])},
|
||||
{:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []},
|
||||
{:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []},
|
||||
|
@ -138,7 +170,7 @@ def to_simple_form(%{data: %{"type" => "Like"}} = activity, user, with_author) d
|
|||
end
|
||||
|
||||
def to_simple_form(%{data: %{"type" => "Announce"}} = activity, user, with_author) do
|
||||
h = fn(str) -> [to_charlist(str)] end
|
||||
h = fn str -> [to_charlist(str)] end
|
||||
|
||||
updated_at = activity.data["published"]
|
||||
inserted_at = activity.data["published"]
|
||||
|
@ -152,6 +184,7 @@ def to_simple_form(%{data: %{"type" => "Announce"}} = activity, user, with_autho
|
|||
retweeted_xml = to_simple_form(retweeted_activity, retweeted_user, true)
|
||||
|
||||
mentions = activity.recipients |> get_mentions
|
||||
|
||||
[
|
||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
||||
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/share']},
|
||||
|
@ -168,7 +201,7 @@ def to_simple_form(%{data: %{"type" => "Announce"}} = activity, user, with_autho
|
|||
end
|
||||
|
||||
def to_simple_form(%{data: %{"type" => "Follow"}} = activity, user, with_author) do
|
||||
h = fn(str) -> [to_charlist(str)] end
|
||||
h = fn str -> [to_charlist(str)] end
|
||||
|
||||
updated_at = activity.data["published"]
|
||||
inserted_at = activity.data["published"]
|
||||
|
@ -176,26 +209,29 @@ def to_simple_form(%{data: %{"type" => "Follow"}} = activity, user, with_author)
|
|||
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
||||
|
||||
mentions = (activity.recipients || []) |> get_mentions
|
||||
|
||||
[
|
||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
||||
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/follow']},
|
||||
{:id, h.(activity.data["id"])},
|
||||
{:title, ['#{user.nickname} started following #{activity.data["object"]}']},
|
||||
{:content, [type: 'html'], ['#{user.nickname} started following #{activity.data["object"]}']},
|
||||
{:content, [type: 'html'],
|
||||
['#{user.nickname} started following #{activity.data["object"]}']},
|
||||
{:published, h.(inserted_at)},
|
||||
{:updated, h.(updated_at)},
|
||||
{:"activity:object", [
|
||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/person']},
|
||||
{:id, h.(activity.data["object"])},
|
||||
{:uri, h.(activity.data["object"])},
|
||||
]},
|
||||
{:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []},
|
||||
{:"activity:object",
|
||||
[
|
||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/person']},
|
||||
{:id, h.(activity.data["object"])},
|
||||
{:uri, h.(activity.data["object"])}
|
||||
]},
|
||||
{:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []}
|
||||
] ++ mentions ++ author
|
||||
end
|
||||
|
||||
# Only undos of follow for now. Will need to get redone once there are more
|
||||
def to_simple_form(%{data: %{"type" => "Undo"}} = activity, user, with_author) do
|
||||
h = fn(str) -> [to_charlist(str)] end
|
||||
h = fn str -> [to_charlist(str)] end
|
||||
|
||||
updated_at = activity.data["published"]
|
||||
inserted_at = activity.data["published"]
|
||||
|
@ -204,25 +240,28 @@ def to_simple_form(%{data: %{"type" => "Undo"}} = activity, user, with_author) d
|
|||
follow_activity = Activity.get_by_ap_id(activity.data["object"])
|
||||
|
||||
mentions = (activity.recipients || []) |> get_mentions
|
||||
|
||||
[
|
||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
||||
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/unfollow']},
|
||||
{:id, h.(activity.data["id"])},
|
||||
{:title, ['#{user.nickname} stopped following #{follow_activity.data["object"]}']},
|
||||
{:content, [type: 'html'], ['#{user.nickname} stopped following #{follow_activity.data["object"]}']},
|
||||
{:content, [type: 'html'],
|
||||
['#{user.nickname} stopped following #{follow_activity.data["object"]}']},
|
||||
{:published, h.(inserted_at)},
|
||||
{:updated, h.(updated_at)},
|
||||
{:"activity:object", [
|
||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/person']},
|
||||
{:id, h.(follow_activity.data["object"])},
|
||||
{:uri, h.(follow_activity.data["object"])},
|
||||
]},
|
||||
{:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []},
|
||||
{:"activity:object",
|
||||
[
|
||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/person']},
|
||||
{:id, h.(follow_activity.data["object"])},
|
||||
{:uri, h.(follow_activity.data["object"])}
|
||||
]},
|
||||
{:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []}
|
||||
] ++ mentions ++ author
|
||||
end
|
||||
|
||||
def to_simple_form(%{data: %{"type" => "Delete"}} = activity, user, with_author) do
|
||||
h = fn(str) -> [to_charlist(str)] end
|
||||
h = fn str -> [to_charlist(str)] end
|
||||
|
||||
updated_at = activity.data["published"]
|
||||
inserted_at = activity.data["published"]
|
||||
|
@ -237,20 +276,24 @@ def to_simple_form(%{data: %{"type" => "Delete"}} = activity, user, with_author)
|
|||
{:content, [type: 'html'], ['An object was deleted']},
|
||||
{:published, h.(inserted_at)},
|
||||
{:updated, h.(updated_at)}
|
||||
] ++ author
|
||||
] ++ author
|
||||
end
|
||||
|
||||
def to_simple_form(_, _, _), do: nil
|
||||
|
||||
def wrap_with_entry(simple_form) do
|
||||
[{
|
||||
:entry, [
|
||||
xmlns: 'http://www.w3.org/2005/Atom',
|
||||
"xmlns:thr": 'http://purl.org/syndication/thread/1.0',
|
||||
"xmlns:activity": 'http://activitystrea.ms/spec/1.0/',
|
||||
"xmlns:poco": 'http://portablecontacts.net/spec/1.0',
|
||||
"xmlns:ostatus": 'http://ostatus.org/schema/1.0'
|
||||
], simple_form
|
||||
}]
|
||||
[
|
||||
{
|
||||
:entry,
|
||||
[
|
||||
xmlns: 'http://www.w3.org/2005/Atom',
|
||||
"xmlns:thr": 'http://purl.org/syndication/thread/1.0',
|
||||
"xmlns:activity": 'http://activitystrea.ms/spec/1.0/',
|
||||
"xmlns:poco": 'http://portablecontacts.net/spec/1.0',
|
||||
"xmlns:ostatus": 'http://ostatus.org/schema/1.0'
|
||||
],
|
||||
simple_form
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,44 +5,57 @@ defmodule Pleroma.Web.OStatus.FeedRepresenter do
|
|||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
def to_simple_form(user, activities, _users) do
|
||||
most_recent_update = (List.first(activities) || user).updated_at
|
||||
|> NaiveDateTime.to_iso8601
|
||||
most_recent_update =
|
||||
(List.first(activities) || user).updated_at
|
||||
|> NaiveDateTime.to_iso8601()
|
||||
|
||||
h = fn(str) -> [to_charlist(str)] end
|
||||
h = fn str -> [to_charlist(str)] end
|
||||
|
||||
last_activity = List.last(activities)
|
||||
|
||||
entries = activities
|
||||
|> Enum.map(fn(activity) ->
|
||||
{:entry, ActivityRepresenter.to_simple_form(activity, user)}
|
||||
end)
|
||||
|> Enum.filter(fn ({_, form}) -> form end)
|
||||
entries =
|
||||
activities
|
||||
|> Enum.map(fn activity ->
|
||||
{:entry, ActivityRepresenter.to_simple_form(activity, user)}
|
||||
end)
|
||||
|> Enum.filter(fn {_, form} -> form end)
|
||||
|
||||
[{
|
||||
:feed, [
|
||||
xmlns: 'http://www.w3.org/2005/Atom',
|
||||
"xmlns:thr": 'http://purl.org/syndication/thread/1.0',
|
||||
"xmlns:activity": 'http://activitystrea.ms/spec/1.0/',
|
||||
"xmlns:poco": 'http://portablecontacts.net/spec/1.0',
|
||||
"xmlns:ostatus": 'http://ostatus.org/schema/1.0'
|
||||
], [
|
||||
{:id, h.(OStatus.feed_path(user))},
|
||||
{:title, ['#{user.nickname}\'s timeline']},
|
||||
{:updated, h.(most_recent_update)},
|
||||
{:logo, [to_charlist(User.avatar_url(user) |> MediaProxy.url())]},
|
||||
{:link, [rel: 'hub', href: h.(OStatus.pubsub_path(user))], []},
|
||||
{:link, [rel: 'salmon', href: h.(OStatus.salmon_path(user))], []},
|
||||
{:link, [rel: 'self', href: h.(OStatus.feed_path(user)), type: 'application/atom+xml'], []},
|
||||
{:author, UserRepresenter.to_simple_form(user)},
|
||||
] ++
|
||||
if last_activity do
|
||||
[{:link, [rel: 'next',
|
||||
href: to_charlist(OStatus.feed_path(user)) ++ '?max_id=' ++ to_charlist(last_activity.id),
|
||||
type: 'application/atom+xml'], []}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
++ entries
|
||||
}]
|
||||
[
|
||||
{
|
||||
:feed,
|
||||
[
|
||||
xmlns: 'http://www.w3.org/2005/Atom',
|
||||
"xmlns:thr": 'http://purl.org/syndication/thread/1.0',
|
||||
"xmlns:activity": 'http://activitystrea.ms/spec/1.0/',
|
||||
"xmlns:poco": 'http://portablecontacts.net/spec/1.0',
|
||||
"xmlns:ostatus": 'http://ostatus.org/schema/1.0'
|
||||
],
|
||||
[
|
||||
{:id, h.(OStatus.feed_path(user))},
|
||||
{:title, ['#{user.nickname}\'s timeline']},
|
||||
{:updated, h.(most_recent_update)},
|
||||
{:logo, [to_charlist(User.avatar_url(user) |> MediaProxy.url())]},
|
||||
{:link, [rel: 'hub', href: h.(OStatus.pubsub_path(user))], []},
|
||||
{:link, [rel: 'salmon', href: h.(OStatus.salmon_path(user))], []},
|
||||
{:link, [rel: 'self', href: h.(OStatus.feed_path(user)), type: 'application/atom+xml'],
|
||||
[]},
|
||||
{:author, UserRepresenter.to_simple_form(user)}
|
||||
] ++
|
||||
if last_activity do
|
||||
[
|
||||
{:link,
|
||||
[
|
||||
rel: 'next',
|
||||
href:
|
||||
to_charlist(OStatus.feed_path(user)) ++
|
||||
'?max_id=' ++ to_charlist(last_activity.id),
|
||||
type: 'application/atom+xml'
|
||||
], []}
|
||||
]
|
||||
else
|
||||
[]
|
||||
end ++ entries
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,8 @@ defmodule Pleroma.Web.OStatus.FollowHandler do
|
|||
def handle(entry, doc) do
|
||||
with {:ok, actor} <- OStatus.find_make_or_update_user(doc),
|
||||
id when not is_nil(id) <- XML.string_from_xpath("/entry/id", entry),
|
||||
followed_uri when not is_nil(followed_uri) <- XML.string_from_xpath("/entry/activity:object/id", entry),
|
||||
followed_uri when not is_nil(followed_uri) <-
|
||||
XML.string_from_xpath("/entry/activity:object/id", entry),
|
||||
{:ok, followed} <- OStatus.find_or_make_user(followed_uri),
|
||||
{:ok, activity} <- ActivityPub.follow(actor, followed, id, false) do
|
||||
User.follow(actor, followed)
|
||||
|
|
|
@ -13,49 +13,56 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
|
|||
3. A newly generated context id.
|
||||
"""
|
||||
def get_context(entry, inReplyTo) do
|
||||
context = (
|
||||
XML.string_from_xpath("//ostatus:conversation[1]", entry)
|
||||
|| XML.string_from_xpath("//ostatus:conversation[1]/@ref", entry)
|
||||
|| "") |> String.trim
|
||||
context =
|
||||
(XML.string_from_xpath("//ostatus:conversation[1]", entry) ||
|
||||
XML.string_from_xpath("//ostatus:conversation[1]/@ref", entry) || "")
|
||||
|> String.trim()
|
||||
|
||||
with %{data: %{"context" => context}} <- Object.get_cached_by_ap_id(inReplyTo) do
|
||||
context
|
||||
else _e ->
|
||||
if String.length(context) > 0 do
|
||||
context
|
||||
else
|
||||
Utils.generate_context_id
|
||||
end
|
||||
else
|
||||
_e ->
|
||||
if String.length(context) > 0 do
|
||||
context
|
||||
else
|
||||
Utils.generate_context_id()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_people_mentions(entry) do
|
||||
:xmerl_xpath.string('//link[@rel="mentioned" and @ostatus:object-type="http://activitystrea.ms/schema/1.0/person"]', entry)
|
||||
|> Enum.map(fn(person) -> XML.string_from_xpath("@href", person) end)
|
||||
:xmerl_xpath.string(
|
||||
'//link[@rel="mentioned" and @ostatus:object-type="http://activitystrea.ms/schema/1.0/person"]',
|
||||
entry
|
||||
)
|
||||
|> Enum.map(fn person -> XML.string_from_xpath("@href", person) end)
|
||||
end
|
||||
|
||||
def get_collection_mentions(entry) do
|
||||
transmogrify = fn
|
||||
("http://activityschema.org/collection/public") ->
|
||||
"http://activityschema.org/collection/public" ->
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
(group) ->
|
||||
|
||||
group ->
|
||||
group
|
||||
end
|
||||
|
||||
:xmerl_xpath.string('//link[@rel="mentioned" and @ostatus:object-type="http://activitystrea.ms/schema/1.0/collection"]', entry)
|
||||
|> Enum.map(fn(collection) -> XML.string_from_xpath("@href", collection) |> transmogrify.() end)
|
||||
:xmerl_xpath.string(
|
||||
'//link[@rel="mentioned" and @ostatus:object-type="http://activitystrea.ms/schema/1.0/collection"]',
|
||||
entry
|
||||
)
|
||||
|> Enum.map(fn collection -> XML.string_from_xpath("@href", collection) |> transmogrify.() end)
|
||||
end
|
||||
|
||||
def get_mentions(entry) do
|
||||
(get_people_mentions(entry)
|
||||
++ get_collection_mentions(entry))
|
||||
|> Enum.filter(&(&1))
|
||||
(get_people_mentions(entry) ++ get_collection_mentions(entry))
|
||||
|> Enum.filter(& &1)
|
||||
end
|
||||
|
||||
def get_emoji(entry) do
|
||||
try do
|
||||
:xmerl_xpath.string('//link[@rel="emoji"]', entry)
|
||||
|> Enum.reduce(%{}, fn(emoji, acc) ->
|
||||
|> Enum.reduce(%{}, fn emoji, acc ->
|
||||
Map.put(acc, XML.string_from_xpath("@name", emoji), XML.string_from_xpath("@href", emoji))
|
||||
end)
|
||||
rescue
|
||||
|
@ -79,7 +86,8 @@ def fetch_replied_to_activity(entry, inReplyTo) do
|
|||
activity
|
||||
else
|
||||
_e ->
|
||||
with inReplyToHref when not is_nil(inReplyToHref) <- XML.string_from_xpath("//thr:in-reply-to[1]/@href", entry),
|
||||
with inReplyToHref when not is_nil(inReplyToHref) <-
|
||||
XML.string_from_xpath("//thr:in-reply-to[1]/@href", entry),
|
||||
{:ok, [activity | _]} <- OStatus.fetch_activity_from_url(inReplyToHref) do
|
||||
activity
|
||||
else
|
||||
|
@ -107,16 +115,40 @@ def handle_note(entry, doc \\ nil) do
|
|||
date <- XML.string_from_xpath("//published", entry),
|
||||
unlisted <- XML.string_from_xpath("//mastodon:scope", entry) == "unlisted",
|
||||
cc <- if(unlisted, do: ["https://www.w3.org/ns/activitystreams#Public"], else: []),
|
||||
note <- CommonAPI.Utils.make_note_data(actor.ap_id, to, context, content_html, attachments, inReplyToActivity, [], cw),
|
||||
note <-
|
||||
CommonAPI.Utils.make_note_data(
|
||||
actor.ap_id,
|
||||
to,
|
||||
context,
|
||||
content_html,
|
||||
attachments,
|
||||
inReplyToActivity,
|
||||
[],
|
||||
cw
|
||||
),
|
||||
note <- note |> Map.put("id", id) |> Map.put("tag", tags),
|
||||
note <- note |> Map.put("published", date),
|
||||
note <- note |> Map.put("emoji", get_emoji(entry)),
|
||||
note <- add_external_url(note, entry),
|
||||
note <- note |> Map.put("cc", cc),
|
||||
# TODO: Handle this case in make_note_data
|
||||
note <- (if inReplyTo && !inReplyToActivity, do: note |> Map.put("inReplyTo", inReplyTo), else: note)
|
||||
do
|
||||
res = ActivityPub.create(%{to: to, actor: actor, context: context, object: note, published: date, local: false, additional: %{"cc" => cc}})
|
||||
note <-
|
||||
if(
|
||||
inReplyTo && !inReplyToActivity,
|
||||
do: note |> Map.put("inReplyTo", inReplyTo),
|
||||
else: note
|
||||
) do
|
||||
res =
|
||||
ActivityPub.create(%{
|
||||
to: to,
|
||||
actor: actor,
|
||||
context: context,
|
||||
object: note,
|
||||
published: date,
|
||||
local: false,
|
||||
additional: %{"cc" => cc}
|
||||
})
|
||||
|
||||
User.increase_note_count(actor)
|
||||
res
|
||||
else
|
||||
|
|
|
@ -16,7 +16,7 @@ def feed_path(user) do
|
|||
end
|
||||
|
||||
def pubsub_path(user) do
|
||||
"#{Web.base_url}/push/hub/#{user.nickname}"
|
||||
"#{Web.base_url()}/push/hub/#{user.nickname}"
|
||||
end
|
||||
|
||||
def salmon_path(user) do
|
||||
|
@ -24,48 +24,59 @@ def salmon_path(user) do
|
|||
end
|
||||
|
||||
def remote_follow_path do
|
||||
"#{Web.base_url}/ostatus_subscribe?acct={uri}"
|
||||
"#{Web.base_url()}/ostatus_subscribe?acct={uri}"
|
||||
end
|
||||
|
||||
def handle_incoming(xml_string) do
|
||||
with doc when doc != :error <- parse_document(xml_string) do
|
||||
entries = :xmerl_xpath.string('//entry', doc)
|
||||
|
||||
activities = Enum.map(entries, fn (entry) ->
|
||||
{:xmlObj, :string, object_type} = :xmerl_xpath.string('string(/entry/activity:object-type[1])', entry)
|
||||
{:xmlObj, :string, verb} = :xmerl_xpath.string('string(/entry/activity:verb[1])', entry)
|
||||
Logger.debug("Handling #{verb}")
|
||||
activities =
|
||||
Enum.map(entries, fn entry ->
|
||||
{:xmlObj, :string, object_type} =
|
||||
:xmerl_xpath.string('string(/entry/activity:object-type[1])', entry)
|
||||
|
||||
try do
|
||||
case verb do
|
||||
'http://activitystrea.ms/schema/1.0/delete' ->
|
||||
with {:ok, activity} <- DeleteHandler.handle_delete(entry, doc), do: activity
|
||||
'http://activitystrea.ms/schema/1.0/follow' ->
|
||||
with {:ok, activity} <- FollowHandler.handle(entry, doc), do: activity
|
||||
'http://activitystrea.ms/schema/1.0/share' ->
|
||||
with {:ok, activity, retweeted_activity} <- handle_share(entry, doc), do: [activity, retweeted_activity]
|
||||
'http://activitystrea.ms/schema/1.0/favorite' ->
|
||||
with {:ok, activity, favorited_activity} <- handle_favorite(entry, doc), do: [activity, favorited_activity]
|
||||
_ ->
|
||||
case object_type do
|
||||
'http://activitystrea.ms/schema/1.0/note' ->
|
||||
with {:ok, activity} <- NoteHandler.handle_note(entry, doc), do: activity
|
||||
'http://activitystrea.ms/schema/1.0/comment' ->
|
||||
with {:ok, activity} <- NoteHandler.handle_note(entry, doc), do: activity
|
||||
_ ->
|
||||
Logger.error("Couldn't parse incoming document")
|
||||
nil
|
||||
end
|
||||
{:xmlObj, :string, verb} = :xmerl_xpath.string('string(/entry/activity:verb[1])', entry)
|
||||
Logger.debug("Handling #{verb}")
|
||||
|
||||
try do
|
||||
case verb do
|
||||
'http://activitystrea.ms/schema/1.0/delete' ->
|
||||
with {:ok, activity} <- DeleteHandler.handle_delete(entry, doc), do: activity
|
||||
|
||||
'http://activitystrea.ms/schema/1.0/follow' ->
|
||||
with {:ok, activity} <- FollowHandler.handle(entry, doc), do: activity
|
||||
|
||||
'http://activitystrea.ms/schema/1.0/share' ->
|
||||
with {:ok, activity, retweeted_activity} <- handle_share(entry, doc),
|
||||
do: [activity, retweeted_activity]
|
||||
|
||||
'http://activitystrea.ms/schema/1.0/favorite' ->
|
||||
with {:ok, activity, favorited_activity} <- handle_favorite(entry, doc),
|
||||
do: [activity, favorited_activity]
|
||||
|
||||
_ ->
|
||||
case object_type do
|
||||
'http://activitystrea.ms/schema/1.0/note' ->
|
||||
with {:ok, activity} <- NoteHandler.handle_note(entry, doc), do: activity
|
||||
|
||||
'http://activitystrea.ms/schema/1.0/comment' ->
|
||||
with {:ok, activity} <- NoteHandler.handle_note(entry, doc), do: activity
|
||||
|
||||
_ ->
|
||||
Logger.error("Couldn't parse incoming document")
|
||||
nil
|
||||
end
|
||||
end
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("Error occured while handling activity")
|
||||
Logger.error(xml_string)
|
||||
Logger.error(inspect(e))
|
||||
nil
|
||||
end
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("Error occured while handling activity")
|
||||
Logger.error(xml_string)
|
||||
Logger.error(inspect(e))
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(&(&1))
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
{:ok, activities}
|
||||
else
|
||||
|
@ -113,15 +124,20 @@ def get_or_build_object(entry) do
|
|||
|
||||
def get_or_try_fetching(entry) do
|
||||
Logger.debug("Trying to get entry from db")
|
||||
|
||||
with id when not is_nil(id) <- string_from_xpath("//activity:object[1]/id", entry),
|
||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||
{:ok, activity}
|
||||
else _ ->
|
||||
else
|
||||
_ ->
|
||||
Logger.debug("Couldn't get, will try to fetch")
|
||||
with href when not is_nil(href) <- string_from_xpath("//activity:object[1]/link[@type=\"text/html\"]/@href", entry),
|
||||
|
||||
with href when not is_nil(href) <-
|
||||
string_from_xpath("//activity:object[1]/link[@type=\"text/html\"]/@href", entry),
|
||||
{:ok, [favorited_activity]} <- fetch_activity_from_url(href) do
|
||||
{:ok, favorited_activity}
|
||||
else e -> Logger.debug("Couldn't find href: #{inspect(e)}")
|
||||
else
|
||||
e -> Logger.debug("Couldn't find href: #{inspect(e)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -137,20 +153,22 @@ def handle_favorite(entry, doc) do
|
|||
|
||||
def get_attachments(entry) do
|
||||
:xmerl_xpath.string('/entry/link[@rel="enclosure"]', entry)
|
||||
|> Enum.map(fn (enclosure) ->
|
||||
|> Enum.map(fn enclosure ->
|
||||
with href when not is_nil(href) <- string_from_xpath("/link/@href", enclosure),
|
||||
type when not is_nil(type) <- string_from_xpath("/link/@type", enclosure) do
|
||||
%{
|
||||
"type" => "Attachment",
|
||||
"url" => [%{
|
||||
"type" => "Link",
|
||||
"mediaType" => type,
|
||||
"href" => href
|
||||
}]
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => type,
|
||||
"href" => href
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(&(&1))
|
||||
|> Enum.filter(& &1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -166,14 +184,15 @@ def get_content(entry) do
|
|||
def get_cw(entry) do
|
||||
with cw when not is_nil(cw) <- string_from_xpath("/*/summary", entry) do
|
||||
cw
|
||||
else _e -> nil
|
||||
else
|
||||
_e -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_tags(entry) do
|
||||
:xmerl_xpath.string('//category', entry)
|
||||
|> Enum.map(fn (category) -> string_from_xpath("/category/@term", category) end)
|
||||
|> Enum.filter(&(&1))
|
||||
|> Enum.map(fn category -> string_from_xpath("/category/@term", category) end)
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.map(&String.downcase/1)
|
||||
end
|
||||
|
||||
|
@ -184,6 +203,7 @@ def maybe_update(doc, user) do
|
|||
maybe_update_ostatus(doc, user)
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_update_ostatus(doc, user) do
|
||||
old_data = %{
|
||||
avatar: user.avatar,
|
||||
|
@ -196,26 +216,33 @@ def maybe_update_ostatus(doc, user) do
|
|||
avatar <- make_avatar_object(doc),
|
||||
bio <- string_from_xpath("//author[1]/summary", doc),
|
||||
name <- string_from_xpath("//author[1]/poco:displayName", doc),
|
||||
info <- Map.put(user.info, "banner", make_avatar_object(doc, "header") || user.info["banner"]),
|
||||
new_data <- %{avatar: avatar || old_data.avatar, name: name || old_data.name, bio: bio || old_data.bio, info: info || old_data.info},
|
||||
info <-
|
||||
Map.put(user.info, "banner", make_avatar_object(doc, "header") || user.info["banner"]),
|
||||
new_data <- %{
|
||||
avatar: avatar || old_data.avatar,
|
||||
name: name || old_data.name,
|
||||
bio: bio || old_data.bio,
|
||||
info: info || old_data.info
|
||||
},
|
||||
false <- new_data == old_data do
|
||||
change = Ecto.Changeset.change(user, new_data)
|
||||
Repo.update(change)
|
||||
else _ ->
|
||||
{:ok, user}
|
||||
else
|
||||
_ ->
|
||||
{:ok, user}
|
||||
end
|
||||
end
|
||||
|
||||
def find_make_or_update_user(doc) do
|
||||
uri = string_from_xpath("//author/uri[1]", doc)
|
||||
|
||||
with {:ok, user} <- find_or_make_user(uri) do
|
||||
maybe_update(doc, user)
|
||||
end
|
||||
end
|
||||
|
||||
def find_or_make_user(uri) do
|
||||
query = from user in User,
|
||||
where: user.ap_id == ^uri
|
||||
query = from(user in User, where: user.ap_id == ^uri)
|
||||
|
||||
user = Repo.one(query)
|
||||
|
||||
|
@ -236,10 +263,12 @@ def make_user(uri, update \\ false) do
|
|||
avatar: info["avatar"],
|
||||
bio: info["bio"]
|
||||
}
|
||||
|
||||
with false <- update,
|
||||
%User{} = user <- User.get_by_ap_id(data.ap_id) do
|
||||
{:ok, user}
|
||||
else _e -> User.insert_or_update_user(data)
|
||||
else
|
||||
_e -> User.insert_or_update_user(data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -252,12 +281,13 @@ def make_avatar_object(author_doc, rel \\ "avatar") do
|
|||
if href do
|
||||
%{
|
||||
"type" => "Image",
|
||||
"url" =>
|
||||
[%{
|
||||
"type" => "Link",
|
||||
"mediaType" => type,
|
||||
"href" => href
|
||||
}]
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => type,
|
||||
"href" => href
|
||||
}
|
||||
]
|
||||
}
|
||||
else
|
||||
nil
|
||||
|
@ -268,9 +298,10 @@ def gather_user_info(username) do
|
|||
with {:ok, webfinger_data} <- WebFinger.finger(username),
|
||||
{:ok, feed_data} <- Websub.gather_feed_data(webfinger_data["topic"]) do
|
||||
{:ok, Map.merge(webfinger_data, feed_data) |> Map.put("fqn", username)}
|
||||
else e ->
|
||||
Logger.debug(fn -> "Couldn't gather info for #{username}" end)
|
||||
{:error, e}
|
||||
else
|
||||
e ->
|
||||
Logger.debug(fn -> "Couldn't gather info for #{username}" end)
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -284,12 +315,15 @@ def get_atom_url(body) do
|
|||
Regex.match?(@mastodon_regex, body) ->
|
||||
[[_, match]] = Regex.scan(@mastodon_regex, body)
|
||||
{:ok, match}
|
||||
|
||||
Regex.match?(@gs_regex, body) ->
|
||||
[[_, match]] = Regex.scan(@gs_regex, body)
|
||||
{:ok, match}
|
||||
|
||||
Regex.match?(@gs_classic_regex, body) ->
|
||||
[[_, match]] = Regex.scan(@gs_classic_regex, body)
|
||||
{:ok, match}
|
||||
|
||||
true ->
|
||||
Logger.debug(fn -> "Couldn't find Atom link in #{inspect(body)}" end)
|
||||
{:error, "Couldn't find the Atom link"}
|
||||
|
@ -298,7 +332,14 @@ def get_atom_url(body) do
|
|||
|
||||
def fetch_activity_from_atom_url(url) do
|
||||
with true <- String.starts_with?(url, "http"),
|
||||
{:ok, %{body: body, status_code: code}} when code in 200..299 <- @httpoison.get(url, [Accept: "application/atom+xml"], follow_redirect: true, timeout: 10000, recv_timeout: 20000) do
|
||||
{:ok, %{body: body, status_code: code}} when code in 200..299 <-
|
||||
@httpoison.get(
|
||||
url,
|
||||
[Accept: "application/atom+xml"],
|
||||
follow_redirect: true,
|
||||
timeout: 10000,
|
||||
recv_timeout: 20000
|
||||
) do
|
||||
Logger.debug("Got document from #{url}, handling...")
|
||||
handle_incoming(body)
|
||||
else
|
||||
|
@ -310,10 +351,12 @@ def fetch_activity_from_atom_url(url) do
|
|||
|
||||
def fetch_activity_from_html_url(url) do
|
||||
Logger.debug("Trying to fetch #{url}")
|
||||
|
||||
with true <- String.starts_with?(url, "http"),
|
||||
{:ok, %{body: body}} <- @httpoison.get(url, [], follow_redirect: true, timeout: 10000, recv_timeout: 20000),
|
||||
{:ok, %{body: body}} <-
|
||||
@httpoison.get(url, [], follow_redirect: true, timeout: 10000, recv_timeout: 20000),
|
||||
{:ok, atom_url} <- get_atom_url(body) do
|
||||
fetch_activity_from_atom_url(atom_url)
|
||||
fetch_activity_from_atom_url(atom_url)
|
||||
else
|
||||
e ->
|
||||
Logger.debug("Couldn't get #{url}: #{inspect(e)}")
|
||||
|
@ -326,9 +369,10 @@ def fetch_activity_from_url(url) do
|
|||
with {:ok, activities} when length(activities) > 0 <- fetch_activity_from_atom_url(url) do
|
||||
{:ok, activities}
|
||||
else
|
||||
_e -> with {:ok, activities} <- fetch_activity_from_html_url(url) do
|
||||
{:ok, activities}
|
||||
end
|
||||
_e ->
|
||||
with {:ok, activities} <- fetch_activity_from_html_url(url) do
|
||||
{:ok, activities}
|
||||
end
|
||||
end
|
||||
rescue
|
||||
e ->
|
||||
|
|
|
@ -16,23 +16,26 @@ def feed_redirect(conn, %{"nickname" => nickname} = params) do
|
|||
case get_format(conn) do
|
||||
"html" -> Fallback.RedirectController.redirector(conn, nil)
|
||||
"activity+json" -> ActivityPubController.user(conn, params)
|
||||
_ -> redirect conn, external: OStatus.feed_path(user)
|
||||
_ -> redirect(conn, external: OStatus.feed_path(user))
|
||||
end
|
||||
end
|
||||
|
||||
def feed(conn, %{"nickname" => nickname} = params) do
|
||||
user = User.get_cached_by_nickname(nickname)
|
||||
|
||||
query_params = Map.take(params, ["max_id"])
|
||||
|> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
|
||||
query_params =
|
||||
Map.take(params, ["max_id"])
|
||||
|> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
|
||||
|
||||
activities = ActivityPub.fetch_public_activities(query_params)
|
||||
|> Enum.reverse
|
||||
activities =
|
||||
ActivityPub.fetch_public_activities(query_params)
|
||||
|> Enum.reverse()
|
||||
|
||||
response = user
|
||||
|> FeedRepresenter.to_simple_form(activities, [user])
|
||||
|> :xmerl.export_simple(:xmerl_xml)
|
||||
|> to_string
|
||||
response =
|
||||
user
|
||||
|> FeedRepresenter.to_simple_form(activities, [user])
|
||||
|> :xmerl.export_simple(:xmerl_xml)
|
||||
|> to_string
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/atom+xml")
|
||||
|
@ -73,7 +76,7 @@ def object(conn, %{"uuid" => uuid} = params) do
|
|||
else
|
||||
with id <- o_status_url(conn, :object, uuid),
|
||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id),
|
||||
%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
|
||||
"html" -> redirect(conn, to: "/notice/#{activity.id}")
|
||||
_ -> represent_activity(conn, activity, user)
|
||||
|
@ -96,24 +99,27 @@ def activity(conn, %{"uuid" => uuid}) do
|
|||
|
||||
# TODO: Data leak
|
||||
def notice(conn, %{"id" => id}) do
|
||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||
case get_format(conn) do
|
||||
"html" ->
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|> send_file(200, "priv/static/index.html")
|
||||
_ -> represent_activity(conn, activity, user)
|
||||
|
||||
_ ->
|
||||
represent_activity(conn, activity, user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp represent_activity(conn, activity, user) do
|
||||
response = activity
|
||||
|> ActivityRepresenter.to_simple_form(user, true)
|
||||
|> ActivityRepresenter.wrap_with_entry
|
||||
|> :xmerl.export_simple(:xmerl_xml)
|
||||
|> to_string
|
||||
response =
|
||||
activity
|
||||
|> ActivityRepresenter.to_simple_form(user, true)
|
||||
|> ActivityRepresenter.wrap_with_entry()
|
||||
|> :xmerl.export_simple(:xmerl_xml)
|
||||
|> to_string
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/atom+xml")
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
defmodule Pleroma.Web.OStatus.UserRepresenter do
|
||||
alias Pleroma.User
|
||||
|
||||
def to_simple_form(user) do
|
||||
ap_id = to_charlist(user.ap_id)
|
||||
nickname = to_charlist(user.nickname)
|
||||
name = to_charlist(user.name)
|
||||
bio = to_charlist(user.bio)
|
||||
avatar_url = to_charlist(User.avatar_url(user))
|
||||
banner = if banner_url = User.banner_url(user) do
|
||||
[{:link, [rel: 'header', href: banner_url], []}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
ap_enabled = if user.local do
|
||||
[{:ap_enabled, ['true']}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
banner =
|
||||
if banner_url = User.banner_url(user) do
|
||||
[{:link, [rel: 'header', href: banner_url], []}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
ap_enabled =
|
||||
if user.local do
|
||||
[{:ap_enabled, ['true']}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
[
|
||||
{:id, [ap_id]},
|
||||
|
|
|
@ -11,294 +11,305 @@ def user_fetcher(username) do
|
|||
end
|
||||
|
||||
pipeline :api do
|
||||
plug :accepts, ["json"]
|
||||
plug :fetch_session
|
||||
plug Pleroma.Plugs.OAuthPlug
|
||||
plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true}
|
||||
plug(:accepts, ["json"])
|
||||
plug(:fetch_session)
|
||||
plug(Pleroma.Plugs.OAuthPlug)
|
||||
plug(Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true})
|
||||
end
|
||||
|
||||
pipeline :authenticated_api do
|
||||
plug :accepts, ["json"]
|
||||
plug :fetch_session
|
||||
plug Pleroma.Plugs.OAuthPlug
|
||||
plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1}
|
||||
plug(:accepts, ["json"])
|
||||
plug(:fetch_session)
|
||||
plug(Pleroma.Plugs.OAuthPlug)
|
||||
plug(Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1})
|
||||
end
|
||||
|
||||
pipeline :mastodon_html do
|
||||
plug :accepts, ["html"]
|
||||
plug :fetch_session
|
||||
plug Pleroma.Plugs.OAuthPlug
|
||||
plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true}
|
||||
plug(:accepts, ["html"])
|
||||
plug(:fetch_session)
|
||||
plug(Pleroma.Plugs.OAuthPlug)
|
||||
plug(Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true})
|
||||
end
|
||||
|
||||
pipeline :pleroma_html do
|
||||
plug :accepts, ["html"]
|
||||
plug :fetch_session
|
||||
plug Pleroma.Plugs.OAuthPlug
|
||||
plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true}
|
||||
plug(:accepts, ["html"])
|
||||
plug(:fetch_session)
|
||||
plug(Pleroma.Plugs.OAuthPlug)
|
||||
plug(Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true})
|
||||
end
|
||||
|
||||
pipeline :well_known do
|
||||
plug :accepts, ["xml", "xrd+xml", "json", "jrd+json"]
|
||||
plug(:accepts, ["xml", "xrd+xml", "json", "jrd+json"])
|
||||
end
|
||||
|
||||
pipeline :config do
|
||||
plug :accepts, ["json", "xml"]
|
||||
plug(:accepts, ["json", "xml"])
|
||||
end
|
||||
|
||||
pipeline :oauth do
|
||||
plug :accepts, ["html", "json"]
|
||||
plug(:accepts, ["html", "json"])
|
||||
end
|
||||
|
||||
pipeline :pleroma_api do
|
||||
plug :accepts, ["html", "json"]
|
||||
plug(:accepts, ["html", "json"])
|
||||
end
|
||||
|
||||
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
|
||||
pipe_through :pleroma_api
|
||||
get "/password_reset/:token", UtilController, :show_password_reset
|
||||
post "/password_reset", UtilController, :password_reset
|
||||
get "/emoji", UtilController, :emoji
|
||||
pipe_through(:pleroma_api)
|
||||
get("/password_reset/:token", UtilController, :show_password_reset)
|
||||
post("/password_reset", UtilController, :password_reset)
|
||||
get("/emoji", UtilController, :emoji)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.TwitterAPI do
|
||||
pipe_through :pleroma_html
|
||||
get "/ostatus_subscribe", UtilController, :remote_follow
|
||||
post "/ostatus_subscribe", UtilController, :do_remote_follow
|
||||
post "/main/ostatus", UtilController, :remote_subscribe
|
||||
pipe_through(:pleroma_html)
|
||||
get("/ostatus_subscribe", UtilController, :remote_follow)
|
||||
post("/ostatus_subscribe", UtilController, :do_remote_follow)
|
||||
post("/main/ostatus", UtilController, :remote_subscribe)
|
||||
end
|
||||
|
||||
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
|
||||
pipe_through :authenticated_api
|
||||
post "/follow_import", UtilController, :follow_import
|
||||
pipe_through(:authenticated_api)
|
||||
post("/follow_import", UtilController, :follow_import)
|
||||
end
|
||||
|
||||
scope "/oauth", Pleroma.Web.OAuth do
|
||||
get "/authorize", OAuthController, :authorize
|
||||
post "/authorize", OAuthController, :create_authorization
|
||||
post "/token", OAuthController, :token_exchange
|
||||
get("/authorize", OAuthController, :authorize)
|
||||
post("/authorize", OAuthController, :create_authorization)
|
||||
post("/token", OAuthController, :token_exchange)
|
||||
end
|
||||
|
||||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||
pipe_through :authenticated_api
|
||||
pipe_through(:authenticated_api)
|
||||
|
||||
patch "/accounts/update_credentials", MastodonAPIController, :update_credentials
|
||||
get "/accounts/verify_credentials", MastodonAPIController, :verify_credentials
|
||||
get "/accounts/relationships", MastodonAPIController, :relationships
|
||||
get "/accounts/search", MastodonAPIController, :account_search
|
||||
post "/accounts/:id/follow", MastodonAPIController, :follow
|
||||
post "/accounts/:id/unfollow", MastodonAPIController, :unfollow
|
||||
post "/accounts/:id/block", MastodonAPIController, :block
|
||||
post "/accounts/:id/unblock", MastodonAPIController, :unblock
|
||||
post "/accounts/:id/mute", MastodonAPIController, :relationship_noop
|
||||
post "/accounts/:id/unmute", MastodonAPIController, :relationship_noop
|
||||
patch("/accounts/update_credentials", MastodonAPIController, :update_credentials)
|
||||
get("/accounts/verify_credentials", MastodonAPIController, :verify_credentials)
|
||||
get("/accounts/relationships", MastodonAPIController, :relationships)
|
||||
get("/accounts/search", MastodonAPIController, :account_search)
|
||||
post("/accounts/:id/follow", MastodonAPIController, :follow)
|
||||
post("/accounts/:id/unfollow", MastodonAPIController, :unfollow)
|
||||
post("/accounts/:id/block", MastodonAPIController, :block)
|
||||
post("/accounts/:id/unblock", MastodonAPIController, :unblock)
|
||||
post("/accounts/:id/mute", MastodonAPIController, :relationship_noop)
|
||||
post("/accounts/:id/unmute", MastodonAPIController, :relationship_noop)
|
||||
|
||||
post "/follows", MastodonAPIController, :follow
|
||||
post("/follows", MastodonAPIController, :follow)
|
||||
|
||||
get "/blocks", MastodonAPIController, :blocks
|
||||
get("/blocks", MastodonAPIController, :blocks)
|
||||
|
||||
get "/domain_blocks", MastodonAPIController, :empty_array
|
||||
get "/follow_requests", MastodonAPIController, :empty_array
|
||||
get "/mutes", MastodonAPIController, :empty_array
|
||||
get("/domain_blocks", MastodonAPIController, :empty_array)
|
||||
get("/follow_requests", MastodonAPIController, :empty_array)
|
||||
get("/mutes", MastodonAPIController, :empty_array)
|
||||
|
||||
get "/timelines/home", MastodonAPIController, :home_timeline
|
||||
get("/timelines/home", MastodonAPIController, :home_timeline)
|
||||
|
||||
get "/favourites", MastodonAPIController, :favourites
|
||||
get("/favourites", MastodonAPIController, :favourites)
|
||||
|
||||
post "/statuses", MastodonAPIController, :post_status
|
||||
delete "/statuses/:id", MastodonAPIController, :delete_status
|
||||
post("/statuses", MastodonAPIController, :post_status)
|
||||
delete("/statuses/:id", MastodonAPIController, :delete_status)
|
||||
|
||||
post "/statuses/:id/reblog", MastodonAPIController, :reblog_status
|
||||
post "/statuses/:id/favourite", MastodonAPIController, :fav_status
|
||||
post "/statuses/:id/unfavourite", MastodonAPIController, :unfav_status
|
||||
post("/statuses/:id/reblog", MastodonAPIController, :reblog_status)
|
||||
post("/statuses/:id/favourite", MastodonAPIController, :fav_status)
|
||||
post("/statuses/:id/unfavourite", MastodonAPIController, :unfav_status)
|
||||
|
||||
post "/notifications/clear", MastodonAPIController, :clear_notifications
|
||||
post "/notifications/dismiss", MastodonAPIController, :dismiss_notification
|
||||
get "/notifications", MastodonAPIController, :notifications
|
||||
get "/notifications/:id", MastodonAPIController, :get_notification
|
||||
post("/notifications/clear", MastodonAPIController, :clear_notifications)
|
||||
post("/notifications/dismiss", MastodonAPIController, :dismiss_notification)
|
||||
get("/notifications", MastodonAPIController, :notifications)
|
||||
get("/notifications/:id", MastodonAPIController, :get_notification)
|
||||
|
||||
post "/media", MastodonAPIController, :upload
|
||||
post("/media", MastodonAPIController, :upload)
|
||||
end
|
||||
|
||||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||
pipe_through :api
|
||||
get "/instance", MastodonAPIController, :masto_instance
|
||||
get "/instance/peers", MastodonAPIController, :peers
|
||||
post "/apps", MastodonAPIController, :create_app
|
||||
get "/custom_emojis", MastodonAPIController, :custom_emojis
|
||||
pipe_through(:api)
|
||||
get("/instance", MastodonAPIController, :masto_instance)
|
||||
get("/instance/peers", MastodonAPIController, :peers)
|
||||
post("/apps", MastodonAPIController, :create_app)
|
||||
get("/custom_emojis", MastodonAPIController, :custom_emojis)
|
||||
|
||||
get "/timelines/public", MastodonAPIController, :public_timeline
|
||||
get "/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline
|
||||
get("/timelines/public", MastodonAPIController, :public_timeline)
|
||||
get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
|
||||
|
||||
get "/statuses/:id", MastodonAPIController, :get_status
|
||||
get "/statuses/:id/context", MastodonAPIController, :get_context
|
||||
get "/statuses/:id/card", MastodonAPIController, :empty_object
|
||||
get "/statuses/:id/favourited_by", MastodonAPIController, :favourited_by
|
||||
get "/statuses/:id/reblogged_by", MastodonAPIController, :reblogged_by
|
||||
get("/statuses/:id", MastodonAPIController, :get_status)
|
||||
get("/statuses/:id/context", MastodonAPIController, :get_context)
|
||||
get("/statuses/:id/card", MastodonAPIController, :empty_object)
|
||||
get("/statuses/:id/favourited_by", MastodonAPIController, :favourited_by)
|
||||
get("/statuses/:id/reblogged_by", MastodonAPIController, :reblogged_by)
|
||||
|
||||
get "/accounts/:id/statuses", MastodonAPIController, :user_statuses
|
||||
get "/accounts/:id/followers", MastodonAPIController, :followers
|
||||
get "/accounts/:id/following", MastodonAPIController, :following
|
||||
get "/accounts/:id", MastodonAPIController, :user
|
||||
get("/accounts/:id/statuses", MastodonAPIController, :user_statuses)
|
||||
get("/accounts/:id/followers", MastodonAPIController, :followers)
|
||||
get("/accounts/:id/following", MastodonAPIController, :following)
|
||||
get("/accounts/:id", MastodonAPIController, :user)
|
||||
|
||||
get "/search", MastodonAPIController, :search
|
||||
get("/search", MastodonAPIController, :search)
|
||||
end
|
||||
|
||||
scope "/api", Pleroma.Web do
|
||||
pipe_through :config
|
||||
pipe_through(:config)
|
||||
|
||||
get "/help/test", TwitterAPI.UtilController, :help_test
|
||||
post "/help/test", TwitterAPI.UtilController, :help_test
|
||||
get "/statusnet/config", TwitterAPI.UtilController, :config
|
||||
get "/statusnet/version", TwitterAPI.UtilController, :version
|
||||
get("/help/test", TwitterAPI.UtilController, :help_test)
|
||||
post("/help/test", TwitterAPI.UtilController, :help_test)
|
||||
get("/statusnet/config", TwitterAPI.UtilController, :config)
|
||||
get("/statusnet/version", TwitterAPI.UtilController, :version)
|
||||
end
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@registrations_open Keyword.get(@instance, :registrations_open)
|
||||
|
||||
scope "/api", Pleroma.Web do
|
||||
pipe_through :api
|
||||
pipe_through(:api)
|
||||
|
||||
get "/statuses/public_timeline", TwitterAPI.Controller, :public_timeline
|
||||
get "/statuses/public_and_external_timeline", TwitterAPI.Controller, :public_and_external_timeline
|
||||
get "/statuses/networkpublic_timeline", TwitterAPI.Controller, :public_and_external_timeline
|
||||
get "/statuses/user_timeline", TwitterAPI.Controller, :user_timeline
|
||||
get "/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline
|
||||
get "/users/show", TwitterAPI.Controller, :show_user
|
||||
get("/statuses/public_timeline", TwitterAPI.Controller, :public_timeline)
|
||||
|
||||
get "/statuses/followers", TwitterAPI.Controller, :followers
|
||||
get "/statuses/friends", TwitterAPI.Controller, :friends
|
||||
get "/statuses/show/:id", TwitterAPI.Controller, :fetch_status
|
||||
get "/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation
|
||||
get(
|
||||
"/statuses/public_and_external_timeline",
|
||||
TwitterAPI.Controller,
|
||||
:public_and_external_timeline
|
||||
)
|
||||
|
||||
get("/statuses/networkpublic_timeline", TwitterAPI.Controller, :public_and_external_timeline)
|
||||
get("/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
|
||||
get("/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
|
||||
get("/users/show", TwitterAPI.Controller, :show_user)
|
||||
|
||||
get("/statuses/followers", TwitterAPI.Controller, :followers)
|
||||
get("/statuses/friends", TwitterAPI.Controller, :friends)
|
||||
get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status)
|
||||
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 "/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline
|
||||
get("/search", TwitterAPI.Controller, :search)
|
||||
get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline)
|
||||
end
|
||||
|
||||
scope "/api", Pleroma.Web do
|
||||
pipe_through :authenticated_api
|
||||
pipe_through(:authenticated_api)
|
||||
|
||||
get "/account/verify_credentials", TwitterAPI.Controller, :verify_credentials
|
||||
post "/account/verify_credentials", TwitterAPI.Controller, :verify_credentials
|
||||
get("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials)
|
||||
post("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials)
|
||||
|
||||
post "/account/update_profile", TwitterAPI.Controller, :update_profile
|
||||
post "/account/update_profile_banner", TwitterAPI.Controller, :update_banner
|
||||
post "/qvitter/update_background_image", TwitterAPI.Controller, :update_background
|
||||
post("/account/update_profile", TwitterAPI.Controller, :update_profile)
|
||||
post("/account/update_profile_banner", TwitterAPI.Controller, :update_banner)
|
||||
post("/qvitter/update_background_image", TwitterAPI.Controller, :update_background)
|
||||
|
||||
post "/account/most_recent_notification", TwitterAPI.Controller, :update_most_recent_notification
|
||||
post(
|
||||
"/account/most_recent_notification",
|
||||
TwitterAPI.Controller,
|
||||
:update_most_recent_notification
|
||||
)
|
||||
|
||||
get "/statuses/home_timeline", TwitterAPI.Controller, :friends_timeline
|
||||
get "/statuses/friends_timeline", TwitterAPI.Controller, :friends_timeline
|
||||
get "/statuses/mentions", TwitterAPI.Controller, :mentions_timeline
|
||||
get "/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline
|
||||
get("/statuses/home_timeline", TwitterAPI.Controller, :friends_timeline)
|
||||
get("/statuses/friends_timeline", TwitterAPI.Controller, :friends_timeline)
|
||||
get("/statuses/mentions", TwitterAPI.Controller, :mentions_timeline)
|
||||
get("/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline)
|
||||
|
||||
post "/statuses/update", TwitterAPI.Controller, :status_update
|
||||
post "/statuses/retweet/:id", TwitterAPI.Controller, :retweet
|
||||
post "/statuses/destroy/:id", TwitterAPI.Controller, :delete_post
|
||||
post("/statuses/update", TwitterAPI.Controller, :status_update)
|
||||
post("/statuses/retweet/:id", TwitterAPI.Controller, :retweet)
|
||||
post("/statuses/destroy/:id", TwitterAPI.Controller, :delete_post)
|
||||
|
||||
post "/friendships/create", TwitterAPI.Controller, :follow
|
||||
post "/friendships/destroy", TwitterAPI.Controller, :unfollow
|
||||
post "/blocks/create", TwitterAPI.Controller, :block
|
||||
post "/blocks/destroy", TwitterAPI.Controller, :unblock
|
||||
post("/friendships/create", TwitterAPI.Controller, :follow)
|
||||
post("/friendships/destroy", TwitterAPI.Controller, :unfollow)
|
||||
post("/blocks/create", TwitterAPI.Controller, :block)
|
||||
post("/blocks/destroy", TwitterAPI.Controller, :unblock)
|
||||
|
||||
post "/statusnet/media/upload", TwitterAPI.Controller, :upload
|
||||
post "/media/upload", TwitterAPI.Controller, :upload_json
|
||||
post("/statusnet/media/upload", TwitterAPI.Controller, :upload)
|
||||
post("/media/upload", TwitterAPI.Controller, :upload_json)
|
||||
|
||||
post "/favorites/create/:id", TwitterAPI.Controller, :favorite
|
||||
post "/favorites/create", TwitterAPI.Controller, :favorite
|
||||
post "/favorites/destroy/:id", TwitterAPI.Controller, :unfavorite
|
||||
post("/favorites/create/:id", TwitterAPI.Controller, :favorite)
|
||||
post("/favorites/create", TwitterAPI.Controller, :favorite)
|
||||
post("/favorites/destroy/:id", TwitterAPI.Controller, :unfavorite)
|
||||
|
||||
post "/qvitter/update_avatar", TwitterAPI.Controller, :update_avatar
|
||||
post("/qvitter/update_avatar", TwitterAPI.Controller, :update_avatar)
|
||||
|
||||
get "/friends/ids", TwitterAPI.Controller, :friends_ids
|
||||
get "/friendships/no_retweets/ids", TwitterAPI.Controller, :empty_array
|
||||
get("/friends/ids", TwitterAPI.Controller, :friends_ids)
|
||||
get("/friendships/no_retweets/ids", TwitterAPI.Controller, :empty_array)
|
||||
|
||||
get "/mutes/users/ids", TwitterAPI.Controller, :empty_array
|
||||
get("/mutes/users/ids", TwitterAPI.Controller, :empty_array)
|
||||
|
||||
get "/externalprofile/show", TwitterAPI.Controller, :external_profile
|
||||
get("/externalprofile/show", TwitterAPI.Controller, :external_profile)
|
||||
end
|
||||
|
||||
pipeline :ostatus do
|
||||
plug :accepts, ["xml", "atom", "html", "activity+json"]
|
||||
plug(:accepts, ["xml", "atom", "html", "activity+json"])
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web do
|
||||
pipe_through :ostatus
|
||||
pipe_through(:ostatus)
|
||||
|
||||
get "/objects/:uuid", OStatus.OStatusController, :object
|
||||
get "/activities/:uuid", OStatus.OStatusController, :activity
|
||||
get "/notice/:id", OStatus.OStatusController, :notice
|
||||
get "/users/:nickname/feed", OStatus.OStatusController, :feed
|
||||
get "/users/:nickname", OStatus.OStatusController, :feed_redirect
|
||||
get("/objects/:uuid", OStatus.OStatusController, :object)
|
||||
get("/activities/:uuid", OStatus.OStatusController, :activity)
|
||||
get("/notice/:id", OStatus.OStatusController, :notice)
|
||||
get("/users/:nickname/feed", OStatus.OStatusController, :feed)
|
||||
get("/users/:nickname", OStatus.OStatusController, :feed_redirect)
|
||||
|
||||
if @federating do
|
||||
post "/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming
|
||||
post "/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request
|
||||
get "/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation
|
||||
post "/push/subscriptions/:id", Websub.WebsubController, :websub_incoming
|
||||
post("/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming)
|
||||
post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
|
||||
get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation)
|
||||
post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
pipeline :activitypub do
|
||||
plug :accepts, ["activity+json"]
|
||||
plug Pleroma.Web.Plugs.HTTPSignaturePlug
|
||||
plug(:accepts, ["activity+json"])
|
||||
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.ActivityPub do
|
||||
# XXX: not really ostatus
|
||||
pipe_through :ostatus
|
||||
pipe_through(:ostatus)
|
||||
|
||||
get "/users/:nickname/followers", ActivityPubController, :followers
|
||||
get "/users/:nickname/following", ActivityPubController, :following
|
||||
get "/users/:nickname/outbox", ActivityPubController, :outbox
|
||||
get("/users/:nickname/followers", ActivityPubController, :followers)
|
||||
get("/users/:nickname/following", ActivityPubController, :following)
|
||||
get("/users/:nickname/outbox", ActivityPubController, :outbox)
|
||||
end
|
||||
|
||||
if @federating do
|
||||
scope "/", Pleroma.Web.ActivityPub do
|
||||
pipe_through :activitypub
|
||||
post "/users/:nickname/inbox", ActivityPubController, :inbox
|
||||
post "/inbox", ActivityPubController, :inbox
|
||||
pipe_through(:activitypub)
|
||||
post("/users/:nickname/inbox", ActivityPubController, :inbox)
|
||||
post("/inbox", ActivityPubController, :inbox)
|
||||
end
|
||||
|
||||
scope "/.well-known", Pleroma.Web do
|
||||
pipe_through :well_known
|
||||
pipe_through(:well_known)
|
||||
|
||||
get "/host-meta", WebFinger.WebFingerController, :host_meta
|
||||
get "/webfinger", WebFinger.WebFingerController, :webfinger
|
||||
get("/host-meta", WebFinger.WebFingerController, :host_meta)
|
||||
get("/webfinger", WebFinger.WebFingerController, :webfinger)
|
||||
end
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.MastodonAPI do
|
||||
pipe_through :mastodon_html
|
||||
pipe_through(:mastodon_html)
|
||||
|
||||
get "/web/login", MastodonAPIController, :login
|
||||
post "/web/login", MastodonAPIController, :login_post
|
||||
get "/web/*path", MastodonAPIController, :index
|
||||
delete "/auth/sign_out", MastodonAPIController, :logout
|
||||
get("/web/login", MastodonAPIController, :login)
|
||||
post("/web/login", MastodonAPIController, :login_post)
|
||||
get("/web/*path", MastodonAPIController, :index)
|
||||
delete("/auth/sign_out", MastodonAPIController, :logout)
|
||||
end
|
||||
|
||||
pipeline :remote_media do
|
||||
plug :accepts, ["html"]
|
||||
plug(:accepts, ["html"])
|
||||
end
|
||||
|
||||
scope "/proxy/", Pleroma.Web.MediaProxy do
|
||||
pipe_through :remote_media
|
||||
get "/:sig/:url", MediaProxyController, :remote
|
||||
pipe_through(:remote_media)
|
||||
get("/:sig/:url", MediaProxyController, :remote)
|
||||
end
|
||||
|
||||
scope "/", Fallback do
|
||||
get "/*path", RedirectController, :redirector
|
||||
get("/*path", RedirectController, :redirector)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Fallback.RedirectController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
def redirector(conn, _params) do
|
||||
if Mix.env != :test do
|
||||
if Mix.env() != :test do
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|> send_file(200, "priv/static/index.html")
|
||||
|
|
|
@ -38,9 +38,10 @@ def fetch_magic_key(salmon) do
|
|||
def decode_and_validate(magickey, salmon) do
|
||||
[data, type, encoding, alg, sig] = decode(salmon)
|
||||
|
||||
signed_text = [data, type, encoding, alg]
|
||||
|> Enum.map(&Base.url_encode64/1)
|
||||
|> Enum.join(".")
|
||||
signed_text =
|
||||
[data, type, encoding, alg]
|
||||
|> Enum.map(&Base.url_encode64/1)
|
||||
|> Enum.join(".")
|
||||
|
||||
key = decode_key(magickey)
|
||||
|
||||
|
@ -54,22 +55,23 @@ def decode_and_validate(magickey, salmon) do
|
|||
end
|
||||
|
||||
def decode_key("RSA." <> magickey) do
|
||||
make_integer = fn(bin) ->
|
||||
make_integer = fn bin ->
|
||||
list = :erlang.binary_to_list(bin)
|
||||
Enum.reduce(list, 0, fn (el, acc) -> (acc <<< 8) ||| el end)
|
||||
Enum.reduce(list, 0, fn el, acc -> acc <<< 8 ||| el end)
|
||||
end
|
||||
|
||||
[modulus, exponent] = magickey
|
||||
|> String.split(".")
|
||||
|> Enum.map(fn (n) -> Base.url_decode64!(n, padding: false) end)
|
||||
|> Enum.map(make_integer)
|
||||
[modulus, exponent] =
|
||||
magickey
|
||||
|> String.split(".")
|
||||
|> Enum.map(fn n -> Base.url_decode64!(n, padding: false) end)
|
||||
|> Enum.map(make_integer)
|
||||
|
||||
{:RSAPublicKey, modulus, exponent}
|
||||
end
|
||||
|
||||
def encode_key({:RSAPublicKey, modulus, exponent}) do
|
||||
modulus_enc = :binary.encode_unsigned(modulus) |> Base.url_encode64
|
||||
exponent_enc = :binary.encode_unsigned(exponent) |> Base.url_encode64
|
||||
modulus_enc = :binary.encode_unsigned(modulus) |> Base.url_encode64()
|
||||
exponent_enc = :binary.encode_unsigned(exponent) |> Base.url_encode64()
|
||||
|
||||
"RSA.#{modulus_enc}.#{exponent_enc}"
|
||||
end
|
||||
|
@ -78,20 +80,25 @@ def encode_key({:RSAPublicKey, modulus, exponent}) do
|
|||
# We try at compile time to generate natively an RSA key otherwise we fallback on the old way.
|
||||
try do
|
||||
_ = :public_key.generate_key({:rsa, 2048, 65537})
|
||||
|
||||
def generate_rsa_pem do
|
||||
key = :public_key.generate_key({:rsa, 2048, 65537})
|
||||
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
|
||||
pem = :public_key.pem_encode([entry]) |> String.trim_trailing
|
||||
pem = :public_key.pem_encode([entry]) |> String.trim_trailing()
|
||||
{:ok, pem}
|
||||
end
|
||||
rescue
|
||||
_ ->
|
||||
def generate_rsa_pem do
|
||||
port = Port.open({:spawn, "openssl genrsa"}, [:binary])
|
||||
{:ok, pem} = receive do
|
||||
{^port, {:data, pem}} -> {:ok, pem}
|
||||
end
|
||||
|
||||
{:ok, pem} =
|
||||
receive do
|
||||
{^port, {:data, pem}} -> {:ok, pem}
|
||||
end
|
||||
|
||||
Port.close(port)
|
||||
|
||||
if Regex.match?(~r/RSA PRIVATE KEY/, pem) do
|
||||
{:ok, pem}
|
||||
else
|
||||
|
@ -113,17 +120,20 @@ def encode(private_key, doc) do
|
|||
encoding = "base64url"
|
||||
alg = "RSA-SHA256"
|
||||
|
||||
signed_text = [doc, type, encoding, alg]
|
||||
|> Enum.map(&Base.url_encode64/1)
|
||||
|> Enum.join(".")
|
||||
signed_text =
|
||||
[doc, type, encoding, alg]
|
||||
|> Enum.map(&Base.url_encode64/1)
|
||||
|> Enum.join(".")
|
||||
|
||||
signature = signed_text
|
||||
|> :public_key.sign(:sha256, private_key)
|
||||
|> to_string
|
||||
|> Base.url_encode64
|
||||
signature =
|
||||
signed_text
|
||||
|> :public_key.sign(:sha256, private_key)
|
||||
|> to_string
|
||||
|> Base.url_encode64()
|
||||
|
||||
doc_base64 = doc
|
||||
|> Base.url_encode64
|
||||
doc_base64 =
|
||||
doc
|
||||
|> Base.url_encode64()
|
||||
|
||||
# Don't need proper xml building, these strings are safe to leave unescaped
|
||||
salmon = """
|
||||
|
@ -141,20 +151,29 @@ def encode(private_key, doc) do
|
|||
|
||||
def remote_users(%{data: %{"to" => to} = data}) do
|
||||
to = to ++ (data["cc"] || [])
|
||||
|
||||
to
|
||||
|> Enum.map(fn(id) -> User.get_cached_by_ap_id(id) end)
|
||||
|> Enum.filter(fn(user) -> user && !user.local end)
|
||||
|> Enum.map(fn id -> User.get_cached_by_ap_id(id) end)
|
||||
|> Enum.filter(fn user -> user && !user.local end)
|
||||
end
|
||||
|
||||
defp send_to_user(%{info: %{"salmon" => salmon}}, feed, poster) do
|
||||
with {:ok, %{status_code: code}} <- poster.(salmon, feed, [{"Content-Type", "application/magic-envelope+xml"}], timeout: 10000, recv_timeout: 20000, hackney: [pool: :default]) do
|
||||
with {:ok, %{status_code: code}} <-
|
||||
poster.(
|
||||
salmon,
|
||||
feed,
|
||||
[{"Content-Type", "application/magic-envelope+xml"}],
|
||||
timeout: 10000,
|
||||
recv_timeout: 20000,
|
||||
hackney: [pool: :default]
|
||||
) do
|
||||
Logger.debug(fn -> "Pushed to #{salmon}, code #{code}" end)
|
||||
else
|
||||
e -> Logger.debug(fn -> "Pushing Salmon to #{salmon} failed, #{inspect(e)}" end)
|
||||
end
|
||||
end
|
||||
|
||||
defp send_to_user(_,_,_), do: nil
|
||||
defp send_to_user(_, _, _), do: nil
|
||||
|
||||
@supported_activities [
|
||||
"Create",
|
||||
|
@ -165,18 +184,21 @@ defp send_to_user(_,_,_), do: nil
|
|||
"Delete"
|
||||
]
|
||||
def publish(user, activity, poster \\ &@httpoison.post/4)
|
||||
def publish(%{info: %{"keys" => keys}} = user, %{data: %{"type" => type}} = activity, poster) when type in @supported_activities do
|
||||
feed = ActivityRepresenter.to_simple_form(activity, user, true)
|
||||
|> ActivityRepresenter.wrap_with_entry
|
||||
|> :xmerl.export_simple(:xmerl_xml)
|
||||
|> to_string
|
||||
|
||||
def publish(%{info: %{"keys" => keys}} = user, %{data: %{"type" => type}} = activity, poster)
|
||||
when type in @supported_activities do
|
||||
feed =
|
||||
ActivityRepresenter.to_simple_form(activity, user, true)
|
||||
|> ActivityRepresenter.wrap_with_entry()
|
||||
|> :xmerl.export_simple(:xmerl_xml)
|
||||
|> to_string
|
||||
|
||||
if feed do
|
||||
{:ok, private, _} = keys_from_pem(keys)
|
||||
{:ok, feed} = encode(private, feed)
|
||||
|
||||
remote_users(activity)
|
||||
|> Enum.each(fn(remote_user) ->
|
||||
|> Enum.each(fn remote_user ->
|
||||
Task.start(fn ->
|
||||
Logger.debug(fn -> "Sending Salmon to #{remote_user.ap_id}" end)
|
||||
send_to_user(remote_user, feed, poster)
|
||||
|
|
|
@ -5,9 +5,11 @@ defmodule Pleroma.Web.Streamer do
|
|||
|
||||
def start_link do
|
||||
spawn(fn ->
|
||||
Process.sleep(1000 * 30) # 30 seconds
|
||||
# 30 seconds
|
||||
Process.sleep(1000 * 30)
|
||||
GenServer.cast(__MODULE__, %{action: :ping})
|
||||
end)
|
||||
|
||||
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
|
||||
end
|
||||
|
||||
|
@ -25,39 +27,54 @@ def stream(topic, item) do
|
|||
|
||||
def handle_cast(%{action: :ping}, topics) do
|
||||
Map.values(topics)
|
||||
|> List.flatten
|
||||
|> Enum.each(fn (socket) ->
|
||||
|> List.flatten()
|
||||
|> Enum.each(fn socket ->
|
||||
Logger.debug("Sending keepalive ping")
|
||||
send socket.transport_pid, {:text, ""}
|
||||
send(socket.transport_pid, {:text, ""})
|
||||
end)
|
||||
|
||||
spawn(fn ->
|
||||
Process.sleep(1000 * 30) # 30 seconds
|
||||
# 30 seconds
|
||||
Process.sleep(1000 * 30)
|
||||
GenServer.cast(__MODULE__, %{action: :ping})
|
||||
end)
|
||||
|
||||
{:noreply, topics}
|
||||
end
|
||||
|
||||
def handle_cast(%{action: :stream, topic: "user", item: %Notification{} = item}, topics) do
|
||||
topic = "user:#{item.user_id}"
|
||||
Enum.each(topics[topic] || [], fn (socket) ->
|
||||
json = %{
|
||||
event: "notification",
|
||||
payload: Pleroma.Web.MastodonAPI.MastodonAPIController.render_notification(socket.assigns["user"], item) |> Jason.encode!
|
||||
} |> Jason.encode!
|
||||
|
||||
send socket.transport_pid, {:text, json}
|
||||
Enum.each(topics[topic] || [], fn socket ->
|
||||
json =
|
||||
%{
|
||||
event: "notification",
|
||||
payload:
|
||||
Pleroma.Web.MastodonAPI.MastodonAPIController.render_notification(
|
||||
socket.assigns["user"],
|
||||
item
|
||||
)
|
||||
|> Jason.encode!()
|
||||
}
|
||||
|> Jason.encode!()
|
||||
|
||||
send(socket.transport_pid, {:text, json})
|
||||
end)
|
||||
|
||||
{:noreply, topics}
|
||||
end
|
||||
|
||||
def handle_cast(%{action: :stream, topic: "user", item: item}, topics) do
|
||||
Logger.debug("Trying to push to users")
|
||||
recipient_topics = User.get_recipients_from_activity(item)
|
||||
|> Enum.map(fn (%{id: id}) -> "user:#{id}" end)
|
||||
|
||||
Enum.each(recipient_topics, fn (topic) ->
|
||||
recipient_topics =
|
||||
User.get_recipients_from_activity(item)
|
||||
|> Enum.map(fn %{id: id} -> "user:#{id}" end)
|
||||
|
||||
Enum.each(recipient_topics, fn topic ->
|
||||
push_to_socket(topics, topic, item)
|
||||
end)
|
||||
|
||||
{:noreply, topics}
|
||||
end
|
||||
|
||||
|
@ -92,13 +109,21 @@ def handle_cast(m, state) do
|
|||
end
|
||||
|
||||
def push_to_socket(topics, topic, item) do
|
||||
Enum.each(topics[topic] || [], fn (socket) ->
|
||||
json = %{
|
||||
event: "update",
|
||||
payload: Pleroma.Web.MastodonAPI.StatusView.render("status.json", activity: item, for: socket.assigns[:user]) |> Jason.encode!
|
||||
} |> Jason.encode!
|
||||
Enum.each(topics[topic] || [], fn socket ->
|
||||
json =
|
||||
%{
|
||||
event: "update",
|
||||
payload:
|
||||
Pleroma.Web.MastodonAPI.StatusView.render(
|
||||
"status.json",
|
||||
activity: item,
|
||||
for: socket.assigns[:user]
|
||||
)
|
||||
|> Jason.encode!()
|
||||
}
|
||||
|> Jason.encode!()
|
||||
|
||||
send socket.transport_pid, {:text, json}
|
||||
send(socket.transport_pid, {:text, json})
|
||||
end)
|
||||
end
|
||||
|
||||
|
|
|
@ -11,21 +11,21 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
|
||||
def show_password_reset(conn, %{"token" => token}) do
|
||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||
%User{} = user <- Repo.get(User, token.user_id) do
|
||||
render conn, "password_reset.html", %{
|
||||
%User{} = user <- Repo.get(User, token.user_id) do
|
||||
render(conn, "password_reset.html", %{
|
||||
token: token,
|
||||
user: user
|
||||
}
|
||||
})
|
||||
else
|
||||
_e -> render conn, "invalid_token.html"
|
||||
_e -> render(conn, "invalid_token.html")
|
||||
end
|
||||
end
|
||||
|
||||
def password_reset(conn, %{"data" => data}) do
|
||||
with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do
|
||||
render conn, "password_reset_success.html"
|
||||
render(conn, "password_reset_success.html")
|
||||
else
|
||||
_e -> render conn, "password_reset_failed.html"
|
||||
_e -> render(conn, "password_reset_failed.html")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -34,14 +34,19 @@ def help_test(conn, _params) do
|
|||
end
|
||||
|
||||
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nick),
|
||||
avatar = User.avatar_url(user) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nick), avatar = User.avatar_url(user) do
|
||||
conn
|
||||
|> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
|
||||
else
|
||||
_e -> render(conn, "subscribe.html", %{nickname: nick, avatar: nil, error: "Could not find user"})
|
||||
_e ->
|
||||
render(conn, "subscribe.html", %{
|
||||
nickname: nick,
|
||||
avatar: nil,
|
||||
error: "Could not find user"
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do
|
||||
with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
|
||||
%User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do
|
||||
|
@ -49,7 +54,11 @@ def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profil
|
|||
|> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id))
|
||||
else
|
||||
_e ->
|
||||
render(conn, "subscribe.html", %{nickname: nick, avatar: nil, error: "Something went wrong."})
|
||||
render(conn, "subscribe.html", %{
|
||||
nickname: nick,
|
||||
avatar: nil,
|
||||
error: "Something went wrong."
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -64,17 +73,26 @@ def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
|
|||
|> render("follow.html", %{error: err, acct: acct, avatar: avatar, name: name, id: id})
|
||||
else
|
||||
conn
|
||||
|> render("follow_login.html", %{error: false, acct: acct, avatar: avatar, name: name, id: id})
|
||||
|> render("follow_login.html", %{
|
||||
error: false,
|
||||
acct: acct,
|
||||
avatar: avatar,
|
||||
name: name,
|
||||
id: id
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def do_remote_follow(conn, %{"authorization" => %{"name" => username, "password" => password, "id" => id}}) do
|
||||
def do_remote_follow(conn, %{
|
||||
"authorization" => %{"name" => username, "password" => password, "id" => id}
|
||||
}) do
|
||||
followee = Repo.get(User, id)
|
||||
avatar = User.avatar_url(followee)
|
||||
name = followee.nickname
|
||||
|
||||
with %User{} = user <- User.get_cached_by_nickname(username),
|
||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||
%User{} = followed <- Repo.get(User, id),
|
||||
%User{} = followed <- Repo.get(User, id),
|
||||
{:ok, follower} <- User.follow(user, followee),
|
||||
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
||||
conn
|
||||
|
@ -82,9 +100,15 @@ def do_remote_follow(conn, %{"authorization" => %{"name" => username, "password"
|
|||
else
|
||||
_e ->
|
||||
conn
|
||||
|> render("follow_login.html", %{error: "Wrong username or password", id: id, name: name, avatar: avatar})
|
||||
|> render("follow_login.html", %{
|
||||
error: "Wrong username or password",
|
||||
id: id,
|
||||
name: name,
|
||||
avatar: avatar
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
|
||||
with %User{} = followee <- Repo.get(User, id),
|
||||
{:ok, follower} <- User.follow(user, followee),
|
||||
|
@ -93,9 +117,10 @@ def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}
|
|||
|> render("followed.html", %{error: false})
|
||||
else
|
||||
e ->
|
||||
Logger.debug("Remote follow failed with error #{inspect e}")
|
||||
conn
|
||||
|> render("followed.html", %{error: inspect(e)})
|
||||
Logger.debug("Remote follow failed with error #{inspect(e)}")
|
||||
|
||||
conn
|
||||
|> render("followed.html", %{error: inspect(e)})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -107,60 +132,67 @@ def config(conn, _params) do
|
|||
<config>
|
||||
<site>
|
||||
<name>#{Keyword.get(@instance, :name)}</name>
|
||||
<site>#{Web.base_url}</site>
|
||||
<site>#{Web.base_url()}</site>
|
||||
<textlimit>#{Keyword.get(@instance, :limit)}</textlimit>
|
||||
<closed>#{!Keyword.get(@instance, :registrations_open)}</closed>
|
||||
</site>
|
||||
</config>
|
||||
"""
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/xml")
|
||||
|> send_resp(200, response)
|
||||
|
||||
_ ->
|
||||
json(conn, %{
|
||||
site: %{
|
||||
name: Keyword.get(@instance, :name),
|
||||
server: Web.base_url,
|
||||
textlimit: to_string(Keyword.get(@instance, :limit)),
|
||||
closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1")
|
||||
}
|
||||
})
|
||||
site: %{
|
||||
name: Keyword.get(@instance, :name),
|
||||
server: Web.base_url(),
|
||||
textlimit: to_string(Keyword.get(@instance, :limit)),
|
||||
closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1")
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def version(conn, _params) do
|
||||
version = Keyword.get(@instance, :version)
|
||||
|
||||
case get_format(conn) do
|
||||
"xml" ->
|
||||
response = "<version>#{version}</version>"
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/xml")
|
||||
|> send_resp(200, response)
|
||||
_ -> json(conn, version)
|
||||
|
||||
_ ->
|
||||
json(conn, version)
|
||||
end
|
||||
end
|
||||
|
||||
def emoji(conn, _params) do
|
||||
json conn, Enum.into(Formatter.get_custom_emoji(), %{})
|
||||
json(conn, Enum.into(Formatter.get_custom_emoji(), %{}))
|
||||
end
|
||||
|
||||
def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
|
||||
follow_import(conn, %{"list" => File.read!(listfile.path)})
|
||||
end
|
||||
|
||||
def follow_import(%{assigns: %{user: user}} = conn, %{"list" => list}) do
|
||||
Task.start(fn ->
|
||||
String.split(list)
|
||||
|> Enum.map(fn nick ->
|
||||
String.split(list)
|
||||
|> Enum.map(fn nick ->
|
||||
with %User{} = follower <- User.get_cached_by_ap_id(user.ap_id),
|
||||
%User{} = followed <- User.get_or_fetch_by_nickname(nick),
|
||||
{:ok, follower} <- User.follow(follower, followed) do
|
||||
%User{} = followed <- User.get_or_fetch_by_nickname(nick),
|
||||
{:ok, follower} <- User.follow(follower, followed) do
|
||||
ActivityPub.follow(follower, followed)
|
||||
else
|
||||
_e -> Logger.debug "follow_import: following #{nick} failed"
|
||||
_e -> Logger.debug("follow_import: following #{nick} failed")
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
json conn, "job started"
|
||||
json(conn, "job started")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,18 +7,22 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
|
|||
alias Pleroma.Formatter
|
||||
|
||||
defp user_by_ap_id(user_list, ap_id) do
|
||||
Enum.find(user_list, fn (%{ap_id: user_id}) -> ap_id == user_id end)
|
||||
Enum.find(user_list, fn %{ap_id: user_id} -> ap_id == user_id end)
|
||||
end
|
||||
|
||||
def to_map(%Activity{data: %{"type" => "Announce", "actor" => actor, "published" => created_at}} = activity,
|
||||
%{users: users, announced_activity: announced_activity} = opts) do
|
||||
def to_map(
|
||||
%Activity{data: %{"type" => "Announce", "actor" => actor, "published" => created_at}} =
|
||||
activity,
|
||||
%{users: users, announced_activity: announced_activity} = opts
|
||||
) do
|
||||
user = user_by_ap_id(users, actor)
|
||||
created_at = created_at |> Utils.date_to_asctime
|
||||
created_at = created_at |> Utils.date_to_asctime()
|
||||
|
||||
text = "#{user.nickname} retweeted a status."
|
||||
|
||||
announced_user = user_by_ap_id(users, announced_activity.data["actor"])
|
||||
retweeted_status = to_map(announced_activity, Map.merge(%{user: announced_user}, opts))
|
||||
|
||||
%{
|
||||
"id" => activity.id,
|
||||
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
|
||||
|
@ -35,9 +39,11 @@ def to_map(%Activity{data: %{"type" => "Announce", "actor" => actor, "published"
|
|||
}
|
||||
end
|
||||
|
||||
def to_map(%Activity{data: %{"type" => "Like", "published" => created_at}} = activity,
|
||||
%{user: user, liked_activity: liked_activity} = opts) do
|
||||
created_at = created_at |> Utils.date_to_asctime
|
||||
def to_map(
|
||||
%Activity{data: %{"type" => "Like", "published" => created_at}} = activity,
|
||||
%{user: user, liked_activity: liked_activity} = opts
|
||||
) do
|
||||
created_at = created_at |> Utils.date_to_asctime()
|
||||
|
||||
text = "#{user.nickname} favorited a status."
|
||||
|
||||
|
@ -56,12 +62,16 @@ def to_map(%Activity{data: %{"type" => "Like", "published" => created_at}} = act
|
|||
}
|
||||
end
|
||||
|
||||
def to_map(%Activity{data: %{"type" => "Follow", "object" => followed_id}} = activity, %{user: user} = opts) do
|
||||
created_at = activity.data["published"] || (DateTime.to_iso8601(activity.inserted_at))
|
||||
created_at = created_at |> Utils.date_to_asctime
|
||||
def to_map(
|
||||
%Activity{data: %{"type" => "Follow", "object" => followed_id}} = activity,
|
||||
%{user: user} = opts
|
||||
) do
|
||||
created_at = activity.data["published"] || DateTime.to_iso8601(activity.inserted_at)
|
||||
created_at = created_at |> Utils.date_to_asctime()
|
||||
|
||||
followed = User.get_cached_by_ap_id(followed_id)
|
||||
text = "#{user.nickname} started following #{followed.nickname}"
|
||||
|
||||
%{
|
||||
"id" => activity.id,
|
||||
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
|
||||
|
@ -79,10 +89,16 @@ def to_map(%Activity{data: %{"type" => "Follow", "object" => followed_id}} = act
|
|||
|
||||
# TODO:
|
||||
# Make this more proper. Just a placeholder to not break the frontend.
|
||||
def to_map(%Activity{data: %{"type" => "Undo", "published" => created_at, "object" => undid_activity }} = activity, %{user: user} = opts) do
|
||||
created_at = created_at |> Utils.date_to_asctime
|
||||
def to_map(
|
||||
%Activity{
|
||||
data: %{"type" => "Undo", "published" => created_at, "object" => undid_activity}
|
||||
} = activity,
|
||||
%{user: user} = opts
|
||||
) do
|
||||
created_at = created_at |> Utils.date_to_asctime()
|
||||
|
||||
text = "#{user.nickname} undid the action at #{undid_activity}"
|
||||
|
||||
%{
|
||||
"id" => activity.id,
|
||||
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
|
||||
|
@ -98,8 +114,12 @@ def to_map(%Activity{data: %{"type" => "Undo", "published" => created_at, "objec
|
|||
}
|
||||
end
|
||||
|
||||
def to_map(%Activity{data: %{"type" => "Delete", "published" => created_at, "object" => _ }} = activity, %{user: user} = opts) do
|
||||
created_at = created_at |> Utils.date_to_asctime
|
||||
def to_map(
|
||||
%Activity{data: %{"type" => "Delete", "published" => created_at, "object" => _}} =
|
||||
activity,
|
||||
%{user: user} = opts
|
||||
) do
|
||||
created_at = created_at |> Utils.date_to_asctime()
|
||||
|
||||
%{
|
||||
"id" => activity.id,
|
||||
|
@ -107,7 +127,7 @@ def to_map(%Activity{data: %{"type" => "Delete", "published" => created_at, "obj
|
|||
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
|
||||
"attentions" => [],
|
||||
"statusnet_html" => "deleted notice {{tag",
|
||||
"text" => "deleted notice {{tag" ,
|
||||
"text" => "deleted notice {{tag",
|
||||
"is_local" => activity.local,
|
||||
"is_post_verb" => false,
|
||||
"created_at" => created_at,
|
||||
|
@ -117,8 +137,11 @@ def to_map(%Activity{data: %{"type" => "Delete", "published" => created_at, "obj
|
|||
}
|
||||
end
|
||||
|
||||
def to_map(%Activity{data: %{"object" => %{"content" => content} = object}} = activity, %{user: user} = opts) do
|
||||
created_at = object["published"] |> Utils.date_to_asctime
|
||||
def to_map(
|
||||
%Activity{data: %{"object" => %{"content" => content} = object}} = activity,
|
||||
%{user: user} = opts
|
||||
) do
|
||||
created_at = object["published"] |> Utils.date_to_asctime()
|
||||
like_count = object["like_count"] || 0
|
||||
announcement_count = object["announcement_count"] || 0
|
||||
favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
|
||||
|
@ -126,10 +149,11 @@ def to_map(%Activity{data: %{"object" => %{"content" => content} = object}} = ac
|
|||
|
||||
mentions = opts[:mentioned] || []
|
||||
|
||||
attentions = activity.recipients
|
||||
|> Enum.map(fn (ap_id) -> Enum.find(mentions, fn(user) -> ap_id == user.ap_id end) end)
|
||||
|> Enum.filter(&(&1))
|
||||
|> Enum.map(fn (user) -> UserView.render("show.json", %{user: user, for: opts[:for]}) end)
|
||||
attentions =
|
||||
activity.recipients
|
||||
|> Enum.map(fn ap_id -> Enum.find(mentions, fn user -> ap_id == user.ap_id end) end)
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.map(fn user -> UserView.render("show.json", %{user: user, for: opts[:for]}) end)
|
||||
|
||||
conversation_id = conversation_id(activity)
|
||||
|
||||
|
@ -139,14 +163,17 @@ def to_map(%Activity{data: %{"object" => %{"content" => content} = object}} = ac
|
|||
tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags
|
||||
|
||||
summary = activity.data["object"]["summary"]
|
||||
content = if !!summary and summary != "" do
|
||||
"<span>#{activity.data["object"]["summary"]}</span><br />#{content}</span>"
|
||||
else
|
||||
content
|
||||
end
|
||||
|
||||
html = HtmlSanitizeEx.basic_html(content)
|
||||
|> Formatter.emojify(object["emoji"])
|
||||
content =
|
||||
if !!summary and summary != "" do
|
||||
"<span>#{activity.data["object"]["summary"]}</span><br />#{content}</span>"
|
||||
else
|
||||
content
|
||||
end
|
||||
|
||||
html =
|
||||
HtmlSanitizeEx.basic_html(content)
|
||||
|> Formatter.emojify(object["emoji"])
|
||||
|
||||
%{
|
||||
"id" => activity.id,
|
||||
|
@ -175,7 +202,8 @@ def to_map(%Activity{data: %{"object" => %{"content" => content} = object}} = ac
|
|||
def conversation_id(activity) do
|
||||
with context when not is_nil(context) <- activity.data["context"] do
|
||||
TwitterAPI.context_to_conversation_id(context)
|
||||
else _e -> nil
|
||||
else
|
||||
_e -> nil
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
defmodule Pleroma.Web.TwitterAPI.Representers.BaseRepresenter do
|
||||
defmacro __using__(_opts) do
|
||||
quote do
|
||||
def to_json(object) do to_json(object, %{}) end
|
||||
def to_json(object) do
|
||||
to_json(object, %{})
|
||||
end
|
||||
|
||||
def to_json(object, options) do
|
||||
object
|
||||
|> to_map(options)
|
||||
|> Jason.encode!
|
||||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def enum_to_list(enum, options) do
|
||||
mapping = fn (el) -> to_map(el, options) end
|
||||
mapping = fn el -> to_map(el, options) end
|
||||
Enum.map(enum, mapping)
|
||||
end
|
||||
|
||||
|
@ -17,11 +20,14 @@ def to_map(object) do
|
|||
to_map(object, %{})
|
||||
end
|
||||
|
||||
def enum_to_json(enum) do enum_to_json(enum, %{}) end
|
||||
def enum_to_json(enum) do
|
||||
enum_to_json(enum, %{})
|
||||
end
|
||||
|
||||
def enum_to_json(enum, options) do
|
||||
enum
|
||||
|> enum_to_list(options)
|
||||
|> Jason.encode!
|
||||
|> Jason.encode!()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter do
|
|||
|
||||
def to_map(%Object{data: %{"url" => [url | _]}} = object, _opts) do
|
||||
data = object.data
|
||||
|
||||
%{
|
||||
url: url["href"] |> Pleroma.Web.MediaProxy.url(),
|
||||
mimetype: url["mediaType"],
|
||||
|
|
|
@ -13,37 +13,42 @@ def create_status(%User{} = user, %{"status" => _} = data) do
|
|||
end
|
||||
|
||||
def fetch_friend_statuses(user, opts \\ %{}) do
|
||||
opts = opts
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> Map.put("type", ["Create", "Announce", "Follow", "Like"])
|
||||
opts =
|
||||
opts
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> Map.put("type", ["Create", "Announce", "Follow", "Like"])
|
||||
|
||||
ActivityPub.fetch_activities([user.ap_id | user.following], opts)
|
||||
|> activities_to_statuses(%{for: user})
|
||||
end
|
||||
|
||||
def fetch_public_statuses(user, opts \\ %{}) do
|
||||
opts = opts
|
||||
|> Map.put("local_only", true)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("type", ["Create", "Announce", "Follow"])
|
||||
opts =
|
||||
opts
|
||||
|> Map.put("local_only", true)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("type", ["Create", "Announce", "Follow"])
|
||||
|
||||
ActivityPub.fetch_public_activities(opts)
|
||||
|> activities_to_statuses(%{for: user})
|
||||
end
|
||||
|
||||
def fetch_public_and_external_statuses(user, opts \\ %{}) do
|
||||
opts = opts
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("type", ["Create", "Announce", "Follow"])
|
||||
opts =
|
||||
opts
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("type", ["Create", "Announce", "Follow"])
|
||||
|
||||
ActivityPub.fetch_public_activities(opts)
|
||||
|> activities_to_statuses(%{for: user})
|
||||
end
|
||||
|
||||
def fetch_user_statuses(user, opts \\ %{}) do
|
||||
opts = opts
|
||||
|> Map.put("type", ["Create"])
|
||||
opts =
|
||||
opts
|
||||
|> Map.put("type", ["Create"])
|
||||
|
||||
ActivityPub.fetch_public_activities(opts)
|
||||
|> activities_to_statuses(%{for: user})
|
||||
end
|
||||
|
@ -55,12 +60,16 @@ def fetch_mentions(user, opts \\ %{}) do
|
|||
|
||||
def fetch_conversation(user, id) do
|
||||
with context when is_binary(context) <- conversation_id_to_context(id),
|
||||
activities <- ActivityPub.fetch_activities_for_context(context, %{"blocking_user" => user, "user" => user}),
|
||||
statuses <- activities |> activities_to_statuses(%{for: user})
|
||||
do
|
||||
activities <-
|
||||
ActivityPub.fetch_activities_for_context(context, %{
|
||||
"blocking_user" => user,
|
||||
"user" => user
|
||||
}),
|
||||
statuses <- activities |> activities_to_statuses(%{for: user}) do
|
||||
statuses
|
||||
else _e ->
|
||||
[]
|
||||
else
|
||||
_e ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -74,8 +83,7 @@ def fetch_status(user, id) do
|
|||
def follow(%User{} = follower, params) do
|
||||
with {:ok, %User{} = followed} <- get_user(params),
|
||||
{:ok, follower} <- User.follow(follower, followed),
|
||||
{:ok, activity} <- ActivityPub.follow(follower, followed)
|
||||
do
|
||||
{:ok, activity} <- ActivityPub.follow(follower, followed) do
|
||||
{:ok, follower, followed, activity}
|
||||
else
|
||||
err -> err
|
||||
|
@ -83,16 +91,17 @@ def follow(%User{} = follower, params) do
|
|||
end
|
||||
|
||||
def unfollow(%User{} = follower, params) do
|
||||
with { :ok, %User{} = unfollowed } <- get_user(params),
|
||||
{ :ok, follower, follow_activity } <- User.unfollow(follower, unfollowed),
|
||||
{ :ok, _activity } <- ActivityPub.insert(%{
|
||||
"type" => "Undo",
|
||||
"actor" => follower.ap_id,
|
||||
"object" => follow_activity.data["id"], # get latest Follow for these users
|
||||
"published" => make_date()
|
||||
})
|
||||
do
|
||||
{ :ok, follower, unfollowed }
|
||||
with {:ok, %User{} = unfollowed} <- get_user(params),
|
||||
{:ok, follower, follow_activity} <- User.unfollow(follower, unfollowed),
|
||||
{:ok, _activity} <-
|
||||
ActivityPub.insert(%{
|
||||
"type" => "Undo",
|
||||
"actor" => follower.ap_id,
|
||||
# get latest Follow for these users
|
||||
"object" => follow_activity.data["id"],
|
||||
"published" => make_date()
|
||||
}) do
|
||||
{:ok, follower, unfollowed}
|
||||
else
|
||||
err -> err
|
||||
end
|
||||
|
@ -100,8 +109,7 @@ def unfollow(%User{} = follower, params) do
|
|||
|
||||
def block(%User{} = blocker, params) do
|
||||
with {:ok, %User{} = blocked} <- get_user(params),
|
||||
{:ok, blocker} <- User.block(blocker, blocked)
|
||||
do
|
||||
{:ok, blocker} <- User.block(blocker, blocked) do
|
||||
{:ok, blocker, blocked}
|
||||
else
|
||||
err -> err
|
||||
|
@ -110,8 +118,7 @@ def block(%User{} = blocker, params) do
|
|||
|
||||
def unblock(%User{} = blocker, params) do
|
||||
with {:ok, %User{} = blocked} <- get_user(params),
|
||||
{:ok, blocker} <- User.unblock(blocker, blocked)
|
||||
do
|
||||
{:ok, blocker} <- User.unblock(blocker, blocked) do
|
||||
{:ok, blocker, blocked}
|
||||
else
|
||||
err -> err
|
||||
|
@ -163,13 +170,15 @@ def upload(%Plug.Upload{} = file, format \\ "xml") do
|
|||
<atom:link rel="enclosure" href="#{href}" type="#{type}"></atom:link>
|
||||
</rsp>
|
||||
"""
|
||||
|
||||
"json" ->
|
||||
%{
|
||||
media_id: object.id,
|
||||
media_id_string: "#{object.id}}",
|
||||
media_url: href,
|
||||
size: 0
|
||||
} |> Jason.encode!
|
||||
}
|
||||
|> Jason.encode!()
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -189,9 +198,11 @@ def register_user(params) do
|
|||
{:ok, user}
|
||||
else
|
||||
{:error, changeset} ->
|
||||
errors = Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
|
||||
|> Jason.encode!
|
||||
{:error, %{error: errors}}
|
||||
errors =
|
||||
Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
|
||||
|> Jason.encode!()
|
||||
|
||||
{:error, %{error: errors}}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -209,16 +220,20 @@ def get_user(user \\ nil, params) do
|
|||
case target = get_by_id_or_nickname(user_id) do
|
||||
nil ->
|
||||
{:error, "No user with such user_id"}
|
||||
|
||||
_ ->
|
||||
{:ok, target}
|
||||
end
|
||||
|
||||
%{"screen_name" => nickname} ->
|
||||
case target = Repo.get_by(User, nickname: nickname) do
|
||||
nil ->
|
||||
{:error, "No user with such screen_name"}
|
||||
|
||||
_ ->
|
||||
{:ok, target}
|
||||
end
|
||||
|
||||
_ ->
|
||||
if user do
|
||||
{:ok, user}
|
||||
|
@ -229,6 +244,7 @@ def get_user(user \\ nil, params) do
|
|||
end
|
||||
|
||||
defp parse_int(string, default)
|
||||
|
||||
defp parse_int(string, default) when is_binary(string) do
|
||||
with {n, _} <- Integer.parse(string) do
|
||||
n
|
||||
|
@ -236,6 +252,7 @@ defp parse_int(string, default) when is_binary(string) do
|
|||
_e -> default
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_int(_, default), do: default
|
||||
|
||||
def search(user, %{"q" => query} = params) do
|
||||
|
@ -243,19 +260,28 @@ def search(user, %{"q" => query} = params) do
|
|||
page = parse_int(params["page"], 1)
|
||||
offset = (page - 1) * limit
|
||||
|
||||
q = from a in Activity,
|
||||
where: fragment("?->>'type' = 'Create'", a.data),
|
||||
where: fragment("to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)", a.data, ^query),
|
||||
limit: ^limit,
|
||||
offset: ^offset,
|
||||
order_by: [desc: :inserted_at] # this one isn't indexed so psql won't take the wrong index.
|
||||
q =
|
||||
from(
|
||||
a in Activity,
|
||||
where: fragment("?->>'type' = 'Create'", a.data),
|
||||
where:
|
||||
fragment(
|
||||
"to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)",
|
||||
a.data,
|
||||
^query
|
||||
),
|
||||
limit: ^limit,
|
||||
offset: ^offset,
|
||||
# this one isn't indexed so psql won't take the wrong index.
|
||||
order_by: [desc: :inserted_at]
|
||||
)
|
||||
|
||||
activities = Repo.all(q)
|
||||
activities_to_statuses(activities, %{for: user})
|
||||
end
|
||||
|
||||
defp activities_to_statuses(activities, opts) do
|
||||
Enum.map(activities, fn(activity) ->
|
||||
Enum.map(activities, fn activity ->
|
||||
activity_to_status(activity, opts)
|
||||
end)
|
||||
end
|
||||
|
@ -266,7 +292,10 @@ defp activity_to_status(%Activity{data: %{"type" => "Like"}} = activity, opts) d
|
|||
user = User.get_cached_by_ap_id(actor)
|
||||
[liked_activity] = Activity.all_by_object_ap_id(activity.data["object"])
|
||||
|
||||
ActivityRepresenter.to_map(activity, Map.merge(opts, %{user: user, liked_activity: liked_activity}))
|
||||
ActivityRepresenter.to_map(
|
||||
activity,
|
||||
Map.merge(opts, %{user: user, liked_activity: liked_activity})
|
||||
)
|
||||
end
|
||||
|
||||
# For announces, fetch the announced activity and the user.
|
||||
|
@ -276,7 +305,10 @@ defp activity_to_status(%Activity{data: %{"type" => "Announce"}} = activity, opt
|
|||
[announced_activity] = Activity.all_by_object_ap_id(activity.data["object"])
|
||||
announced_actor = User.get_cached_by_ap_id(announced_activity.data["actor"])
|
||||
|
||||
ActivityRepresenter.to_map(activity, Map.merge(opts, %{users: [user, announced_actor], announced_activity: announced_activity}))
|
||||
ActivityRepresenter.to_map(
|
||||
activity,
|
||||
Map.merge(opts, %{users: [user, announced_actor], announced_activity: announced_activity})
|
||||
)
|
||||
end
|
||||
|
||||
defp activity_to_status(%Activity{data: %{"type" => "Delete"}} = activity, opts) do
|
||||
|
@ -289,32 +321,41 @@ defp activity_to_status(activity, opts) do
|
|||
actor = get_in(activity.data, ["actor"])
|
||||
user = User.get_cached_by_ap_id(actor)
|
||||
# mentioned_users = Repo.all(from user in User, where: user.ap_id in ^activity.data["to"])
|
||||
mentioned_users = Enum.map(activity.recipients || [], fn (ap_id) ->
|
||||
if ap_id do
|
||||
User.get_cached_by_ap_id(ap_id)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(&(&1))
|
||||
mentioned_users =
|
||||
Enum.map(activity.recipients || [], fn ap_id ->
|
||||
if ap_id do
|
||||
User.get_cached_by_ap_id(ap_id)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
ActivityRepresenter.to_map(activity, Map.merge(opts, %{user: user, mentioned: mentioned_users}))
|
||||
ActivityRepresenter.to_map(
|
||||
activity,
|
||||
Map.merge(opts, %{user: user, mentioned: mentioned_users})
|
||||
)
|
||||
end
|
||||
|
||||
defp make_date do
|
||||
DateTime.utc_now() |> DateTime.to_iso8601
|
||||
DateTime.utc_now() |> DateTime.to_iso8601()
|
||||
end
|
||||
|
||||
def context_to_conversation_id(context) do
|
||||
with %Object{id: id} <- Object.get_cached_by_ap_id(context) do
|
||||
id
|
||||
else _e ->
|
||||
else
|
||||
_e ->
|
||||
changeset = Object.context_mapping(context)
|
||||
|
||||
case Repo.insert(changeset) do
|
||||
{:ok, %{id: id}} -> id
|
||||
{:ok, %{id: id}} ->
|
||||
id
|
||||
|
||||
# This should be solved by an upsert, but it seems ecto
|
||||
# has problems accessing the constraint inside the jsonb.
|
||||
{:error, _} -> Object.get_cached_by_ap_id(context).id
|
||||
{:error, _} ->
|
||||
Object.get_cached_by_ap_id(context).id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -322,8 +363,9 @@ def context_to_conversation_id(context) do
|
|||
def conversation_id_to_context(id) do
|
||||
with %Object{data: %{"id" => context}} <- Repo.get(Object, id) do
|
||||
context
|
||||
else _e ->
|
||||
{:error, "No such conversation"}
|
||||
else
|
||||
_e ->
|
||||
{:error, "No such conversation"}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -331,12 +373,15 @@ def get_external_profile(for_user, uri) do
|
|||
with %User{} = user <- User.get_or_fetch(uri) do
|
||||
spawn(fn ->
|
||||
with url <- user.info["topic"],
|
||||
{:ok, %{body: body}} <- @httpoison.get(url, [], follow_redirect: true, timeout: 10000, recv_timeout: 20000) do
|
||||
{:ok, %{body: body}} <-
|
||||
@httpoison.get(url, [], follow_redirect: true, timeout: 10000, recv_timeout: 20000) do
|
||||
OStatus.handle_incoming(body)
|
||||
end
|
||||
end)
|
||||
|
||||
{:ok, UserView.render("show.json", %{user: user, for: for_user})}
|
||||
else _e ->
|
||||
else
|
||||
_e ->
|
||||
{:error, "Couldn't find user"}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,7 +16,8 @@ def verify_credentials(%{assigns: %{user: user}} = conn, _params) do
|
|||
|
||||
def status_update(%{assigns: %{user: user}} = conn, %{"status" => _} = status_data) do
|
||||
with media_ids <- extract_media_ids(status_data),
|
||||
{:ok, activity} <- TwitterAPI.create_status(user, Map.put(status_data, "media_ids", media_ids)) do
|
||||
{:ok, activity} <-
|
||||
TwitterAPI.create_status(user, Map.put(status_data, "media_ids", media_ids)) do
|
||||
conn
|
||||
|> json(ActivityRepresenter.to_map(activity, %{user: user}))
|
||||
else
|
||||
|
@ -35,10 +36,10 @@ defp empty_status_reply(conn) do
|
|||
defp extract_media_ids(status_data) do
|
||||
with media_ids when not is_nil(media_ids) <- status_data["media_ids"],
|
||||
split_ids <- String.split(media_ids, ","),
|
||||
clean_ids <- Enum.reject(split_ids, fn (id) -> String.length(id) == 0 end)
|
||||
do
|
||||
clean_ids
|
||||
else _e -> []
|
||||
clean_ids <- Enum.reject(split_ids, fn id -> String.length(id) == 0 end) do
|
||||
clean_ids
|
||||
else
|
||||
_e -> []
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -69,9 +70,9 @@ def friends_timeline(%{assigns: %{user: user}} = conn, params) do
|
|||
def show_user(conn, params) do
|
||||
with {:ok, shown} <- TwitterAPI.get_user(params) do
|
||||
if user = conn.assigns.user do
|
||||
render conn, UserView, "show.json", %{user: shown, for: user}
|
||||
render(conn, UserView, "show.json", %{user: shown, for: user})
|
||||
else
|
||||
render conn, UserView, "show.json", %{user: shown}
|
||||
render(conn, UserView, "show.json", %{user: shown})
|
||||
end
|
||||
else
|
||||
{:error, msg} ->
|
||||
|
@ -83,9 +84,11 @@ def user_timeline(%{assigns: %{user: user}} = conn, params) do
|
|||
case TwitterAPI.get_user(user, params) do
|
||||
{:ok, target_user} ->
|
||||
params = Map.merge(params, %{"actor_id" => target_user.ap_id, "whole_db" => true})
|
||||
statuses = TwitterAPI.fetch_user_statuses(user, params)
|
||||
statuses = TwitterAPI.fetch_user_statuses(user, params)
|
||||
|
||||
conn
|
||||
|> json_reply(200, statuses |> Jason.encode!)
|
||||
|> json_reply(200, statuses |> Jason.encode!())
|
||||
|
||||
{:error, msg} ->
|
||||
bad_request_reply(conn, msg)
|
||||
end
|
||||
|
@ -103,29 +106,36 @@ def follow(%{assigns: %{user: user}} = conn, params) do
|
|||
case TwitterAPI.follow(user, params) do
|
||||
{:ok, user, followed, _activity} ->
|
||||
render(conn, UserView, "show.json", %{user: followed, for: user})
|
||||
{:error, msg} -> forbidden_json_reply(conn, msg)
|
||||
|
||||
{:error, msg} ->
|
||||
forbidden_json_reply(conn, msg)
|
||||
end
|
||||
end
|
||||
|
||||
def block(%{assigns: %{user: user}} = conn, params) do
|
||||
case TwitterAPI.block(user, params) do
|
||||
{:ok, user, blocked} ->
|
||||
render conn, UserView, "show.json", %{user: blocked, for: user}
|
||||
{:error, msg} -> forbidden_json_reply(conn, msg)
|
||||
render(conn, UserView, "show.json", %{user: blocked, for: user})
|
||||
|
||||
{:error, msg} ->
|
||||
forbidden_json_reply(conn, msg)
|
||||
end
|
||||
end
|
||||
|
||||
def unblock(%{assigns: %{user: user}} = conn, params) do
|
||||
case TwitterAPI.unblock(user, params) do
|
||||
{:ok, user, blocked} ->
|
||||
render conn, UserView, "show.json", %{user: blocked, for: user}
|
||||
{:error, msg} -> forbidden_json_reply(conn, msg)
|
||||
render(conn, UserView, "show.json", %{user: blocked, for: user})
|
||||
|
||||
{:error, msg} ->
|
||||
forbidden_json_reply(conn, msg)
|
||||
end
|
||||
end
|
||||
|
||||
def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with {:ok, delete} <- CommonAPI.delete(id, user) do
|
||||
json = ActivityRepresenter.to_json(delete, %{user: user, for: user})
|
||||
|
||||
conn
|
||||
|> json_reply(200, json)
|
||||
end
|
||||
|
@ -135,14 +145,16 @@ def unfollow(%{assigns: %{user: user}} = conn, params) do
|
|||
case TwitterAPI.unfollow(user, params) do
|
||||
{:ok, user, unfollowed} ->
|
||||
render(conn, UserView, "show.json", %{user: unfollowed, for: user})
|
||||
{:error, msg} -> forbidden_json_reply(conn, msg)
|
||||
|
||||
{:error, msg} ->
|
||||
forbidden_json_reply(conn, msg)
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||
true <- ActivityPub.visible_for_user?(activity, user) do
|
||||
render conn, ActivityView, "activity.json", %{activity: activity, for: user}
|
||||
render(conn, ActivityView, "activity.json", %{activity: activity, for: user})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -156,6 +168,7 @@ def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|||
|
||||
def upload(conn, %{"media" => media}) do
|
||||
response = TwitterAPI.upload(media)
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/atom+xml")
|
||||
|> send_resp(200, response)
|
||||
|
@ -163,12 +176,14 @@ def upload(conn, %{"media" => media}) do
|
|||
|
||||
def upload_json(conn, %{"media" => media}) do
|
||||
response = TwitterAPI.upload(media, "json")
|
||||
|
||||
conn
|
||||
|> json_reply(200, response)
|
||||
end
|
||||
|
||||
def get_by_id_or_ap_id(id) do
|
||||
activity = Repo.get(Activity, id) || Activity.get_create_activity_by_object_ap_id(id)
|
||||
|
||||
if activity.data["type"] == "Create" do
|
||||
activity
|
||||
else
|
||||
|
@ -199,8 +214,8 @@ def register(conn, params) do
|
|||
render(conn, UserView, "show.json", %{user: user})
|
||||
else
|
||||
{:error, errors} ->
|
||||
conn
|
||||
|> json_reply(400, Jason.encode!(errors))
|
||||
conn
|
||||
|> json_reply(400, Jason.encode!(errors))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -219,8 +234,9 @@ def update_banner(%{assigns: %{user: user}} = conn, params) do
|
|||
change <- User.info_changeset(user, %{info: new_info}),
|
||||
{:ok, user} <- User.update_and_set_cache(change) do
|
||||
CommonAPI.update(user)
|
||||
%{"url" => [ %{ "href" => href } | _ ]} = object.data
|
||||
response = %{ url: href } |> Jason.encode!
|
||||
%{"url" => [%{"href" => href} | _]} = object.data
|
||||
response = %{url: href} |> Jason.encode!()
|
||||
|
||||
conn
|
||||
|> json_reply(200, response)
|
||||
end
|
||||
|
@ -231,8 +247,9 @@ def update_background(%{assigns: %{user: user}} = conn, params) do
|
|||
new_info <- Map.put(user.info, "background", object.data),
|
||||
change <- User.info_changeset(user, %{info: new_info}),
|
||||
{:ok, _user} <- User.update_and_set_cache(change) do
|
||||
%{"url" => [ %{ "href" => href } | _ ]} = object.data
|
||||
response = %{ url: href } |> Jason.encode!
|
||||
%{"url" => [%{"href" => href} | _]} = object.data
|
||||
response = %{url: href} |> Jason.encode!()
|
||||
|
||||
conn
|
||||
|> json_reply(200, response)
|
||||
end
|
||||
|
@ -285,9 +302,10 @@ def friends(conn, params) do
|
|||
|
||||
def friends_ids(%{assigns: %{user: user}} = conn, _params) do
|
||||
with {:ok, friends} <- User.get_friends(user) do
|
||||
ids = friends
|
||||
|> Enum.map(fn x -> x.id end)
|
||||
|> Jason.encode!
|
||||
ids =
|
||||
friends
|
||||
|> Enum.map(fn x -> x.id end)
|
||||
|> Jason.encode!()
|
||||
|
||||
json(conn, ids)
|
||||
else
|
||||
|
@ -300,11 +318,12 @@ def empty_array(conn, _params) do
|
|||
end
|
||||
|
||||
def update_profile(%{assigns: %{user: user}} = conn, params) do
|
||||
params = if bio = params["description"] do
|
||||
Map.put(params, "bio", bio)
|
||||
else
|
||||
params
|
||||
end
|
||||
params =
|
||||
if bio = params["description"] do
|
||||
Map.put(params, "bio", bio)
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
with changeset <- User.update_changeset(user, params),
|
||||
{:ok, user} <- User.update_and_set_cache(changeset) do
|
||||
|
@ -339,6 +358,6 @@ defp forbidden_json_reply(conn, error_message) do
|
|||
end
|
||||
|
||||
defp error_json(conn, error_message) do
|
||||
%{"error" => error_message, "request" => conn.request_path} |> Jason.encode!
|
||||
%{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
|
|||
|
||||
def render("activity.json", %{activity: %{data: %{"type" => "Announce"}} = activity} = opts) do
|
||||
user = User.get_by_ap_id(activity.data["actor"])
|
||||
created_at = activity.data["published"] |> Utils.date_to_asctime
|
||||
created_at = activity.data["published"] |> Utils.date_to_asctime()
|
||||
announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
|
||||
|
||||
text = "#{user.nickname} retweeted a status."
|
||||
|
@ -37,8 +37,10 @@ def render("activity.json", %{activity: %{data: %{"type" => "Announce"}} = activ
|
|||
def render("activity.json", %{activity: %{data: %{"type" => "Like"}} = activity} = opts) do
|
||||
user = User.get_cached_by_ap_id(activity.data["actor"])
|
||||
liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
|
||||
created_at = activity.data["published"]
|
||||
|> Utils.date_to_asctime
|
||||
|
||||
created_at =
|
||||
activity.data["published"]
|
||||
|> Utils.date_to_asctime()
|
||||
|
||||
text = "#{user.nickname} favorited a status."
|
||||
|
||||
|
@ -57,20 +59,24 @@ def render("activity.json", %{activity: %{data: %{"type" => "Like"}} = activity}
|
|||
}
|
||||
end
|
||||
|
||||
def render("activity.json", %{activity: %{data: %{"type" => "Create", "object" => object}} = activity} = opts) do
|
||||
def render(
|
||||
"activity.json",
|
||||
%{activity: %{data: %{"type" => "Create", "object" => object}} = activity} = opts
|
||||
) do
|
||||
actor = get_in(activity.data, ["actor"])
|
||||
user = User.get_cached_by_ap_id(actor)
|
||||
|
||||
created_at = object["published"] |> Utils.date_to_asctime
|
||||
created_at = object["published"] |> Utils.date_to_asctime()
|
||||
like_count = object["like_count"] || 0
|
||||
announcement_count = object["announcement_count"] || 0
|
||||
favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
|
||||
repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || [])
|
||||
|
||||
attentions = activity.recipients
|
||||
|> Enum.map(fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end)
|
||||
|> Enum.filter(&(&1))
|
||||
|> Enum.map(fn (user) -> UserView.render("show.json", %{user: user, for: opts[:for]}) end)
|
||||
attentions =
|
||||
activity.recipients
|
||||
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.map(fn user -> UserView.render("show.json", %{user: user, for: opts[:for]}) end)
|
||||
|
||||
conversation_id = conversation_id(activity)
|
||||
|
||||
|
@ -81,14 +87,17 @@ def render("activity.json", %{activity: %{data: %{"type" => "Create", "object" =
|
|||
|
||||
summary = activity.data["object"]["summary"]
|
||||
content = object["content"]
|
||||
content = if !!summary and summary != "" do
|
||||
"<span>#{activity.data["object"]["summary"]}</span><br />#{content}</span>"
|
||||
else
|
||||
content
|
||||
end
|
||||
|
||||
html = HtmlSanitizeEx.basic_html(content)
|
||||
|> Formatter.emojify(object["emoji"])
|
||||
content =
|
||||
if !!summary and summary != "" do
|
||||
"<span>#{activity.data["object"]["summary"]}</span><br />#{content}</span>"
|
||||
else
|
||||
content
|
||||
end
|
||||
|
||||
html =
|
||||
HtmlSanitizeEx.basic_html(content)
|
||||
|> Formatter.emojify(object["emoji"])
|
||||
|
||||
%{
|
||||
"id" => activity.id,
|
||||
|
@ -117,7 +126,8 @@ def render("activity.json", %{activity: %{data: %{"type" => "Create", "object" =
|
|||
defp conversation_id(activity) do
|
||||
with context when not is_nil(context) <- activity.data["context"] do
|
||||
TwitterAPI.context_to_conversation_id(context)
|
||||
else _e -> nil
|
||||
else
|
||||
_e -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,20 +14,22 @@ def render("index.json", %{users: users, for: user}) do
|
|||
|
||||
def render("user.json", %{user: user = %User{}} = assigns) do
|
||||
image = User.avatar_url(user) |> MediaProxy.url()
|
||||
{following, follows_you, statusnet_blocking} = if assigns[:for] do
|
||||
{
|
||||
User.following?(assigns[:for], user),
|
||||
User.following?(user, assigns[:for]),
|
||||
User.blocks?(assigns[:for], user)
|
||||
}
|
||||
else
|
||||
{false, false, false}
|
||||
end
|
||||
|
||||
{following, follows_you, statusnet_blocking} =
|
||||
if assigns[:for] do
|
||||
{
|
||||
User.following?(assigns[:for], user),
|
||||
User.following?(user, assigns[:for]),
|
||||
User.blocks?(assigns[:for], user)
|
||||
}
|
||||
else
|
||||
{false, false, false}
|
||||
end
|
||||
|
||||
user_info = User.get_cached_user_info(user)
|
||||
|
||||
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),
|
||||
"favourites_count" => 0,
|
||||
"followers_count" => user_info[:follower_count],
|
||||
|
@ -59,9 +61,14 @@ def render("user.json", %{user: user = %User{}} = assigns) do
|
|||
end
|
||||
end
|
||||
|
||||
def render("short.json", %{user: %User{
|
||||
nickname: nickname, id: id, ap_id: ap_id, name: name
|
||||
}}) do
|
||||
def render("short.json", %{
|
||||
user: %User{
|
||||
nickname: nickname,
|
||||
id: id,
|
||||
ap_id: ap_id,
|
||||
name: name
|
||||
}
|
||||
}) do
|
||||
%{
|
||||
"fullname" => name,
|
||||
"id" => id,
|
||||
|
@ -71,6 +78,6 @@ def render("short.json", %{user: %User{
|
|||
}
|
||||
end
|
||||
|
||||
defp image_url(%{"url" => [ %{ "href" => href } | _ ]}), do: href
|
||||
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
|
||||
defp image_url(_), do: nil
|
||||
end
|
||||
|
|
|
@ -12,6 +12,6 @@ def render("500.json", _assigns) do
|
|||
# In case no render clause matches or no
|
||||
# template is found, let's render it as 500
|
||||
def template_not_found(_template, assigns) do
|
||||
render "500.json", assigns
|
||||
render("500.json", assigns)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,8 +26,9 @@ def controller do
|
|||
|
||||
def view do
|
||||
quote do
|
||||
use Phoenix.View, root: "lib/pleroma/web/templates",
|
||||
namespace: Pleroma.Web
|
||||
use Phoenix.View,
|
||||
root: "lib/pleroma/web/templates",
|
||||
namespace: Pleroma.Web
|
||||
|
||||
# Import convenience functions from controllers
|
||||
import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
|
||||
|
@ -59,6 +60,6 @@ defmacro __using__(which) when is_atom(which) do
|
|||
end
|
||||
|
||||
def base_url do
|
||||
Pleroma.Web.Endpoint.url
|
||||
Pleroma.Web.Endpoint.url()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,43 +8,56 @@ defmodule Pleroma.Web.WebFinger do
|
|||
require Logger
|
||||
|
||||
def host_meta do
|
||||
base_url = Web.base_url
|
||||
base_url = Web.base_url()
|
||||
|
||||
{
|
||||
:XRD, %{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"},
|
||||
:XRD,
|
||||
%{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"},
|
||||
{
|
||||
:Link, %{rel: "lrdd", type: "application/xrd+xml", template: "#{base_url}/.well-known/webfinger?resource={uri}"}
|
||||
:Link,
|
||||
%{
|
||||
rel: "lrdd",
|
||||
type: "application/xrd+xml",
|
||||
template: "#{base_url}/.well-known/webfinger?resource={uri}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|> XmlBuilder.to_doc
|
||||
|> XmlBuilder.to_doc()
|
||||
end
|
||||
|
||||
def webfinger(resource, "JSON") do
|
||||
host = Pleroma.Web.Endpoint.host
|
||||
host = Pleroma.Web.Endpoint.host()
|
||||
regex = ~r/(acct:)?(?<username>\w+)@#{host}/
|
||||
|
||||
with %{"username" => username} <- Regex.named_captures(regex, resource) do
|
||||
user = User.get_by_nickname(username)
|
||||
{:ok, represent_user(user, "JSON")}
|
||||
else _e ->
|
||||
with user when not is_nil(user) <- User.get_cached_by_ap_id(resource) do
|
||||
{:ok, represent_user(user, "JSON")}
|
||||
else _e ->
|
||||
{:error, "Couldn't find user"}
|
||||
end
|
||||
else
|
||||
_e ->
|
||||
with user when not is_nil(user) <- User.get_cached_by_ap_id(resource) do
|
||||
{:ok, represent_user(user, "JSON")}
|
||||
else
|
||||
_e ->
|
||||
{:error, "Couldn't find user"}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def webfinger(resource, "XML") do
|
||||
host = Pleroma.Web.Endpoint.host
|
||||
host = Pleroma.Web.Endpoint.host()
|
||||
regex = ~r/(acct:)?(?<username>\w+)@#{host}/
|
||||
|
||||
with %{"username" => username} <- Regex.named_captures(regex, resource) do
|
||||
user = User.get_by_nickname(username)
|
||||
{:ok, represent_user(user, "XML")}
|
||||
else _e ->
|
||||
with user when not is_nil(user) <- User.get_cached_by_ap_id(resource) do
|
||||
{:ok, represent_user(user, "XML")}
|
||||
else _e ->
|
||||
{:error, "Couldn't find user"}
|
||||
end
|
||||
else
|
||||
_e ->
|
||||
with user when not is_nil(user) <- User.get_cached_by_ap_id(resource) do
|
||||
{:ok, represent_user(user, "XML")}
|
||||
else
|
||||
_e ->
|
||||
{:error, "Couldn't find user"}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -52,16 +65,28 @@ def represent_user(user, "JSON") do
|
|||
{:ok, user} = ensure_keys_present(user)
|
||||
{:ok, _private, public} = Salmon.keys_from_pem(user.info["keys"])
|
||||
magic_key = Salmon.encode_key(public)
|
||||
|
||||
%{
|
||||
"subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host}",
|
||||
"subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}",
|
||||
"aliases" => [user.ap_id],
|
||||
"links" => [
|
||||
%{"rel" => "http://schemas.google.com/g/2010#updates-from", "type" => "application/atom+xml", "href" => OStatus.feed_path(user)},
|
||||
%{"rel" => "http://webfinger.net/rel/profile-page", "type" => "text/html", "href" => user.ap_id},
|
||||
%{
|
||||
"rel" => "http://schemas.google.com/g/2010#updates-from",
|
||||
"type" => "application/atom+xml",
|
||||
"href" => OStatus.feed_path(user)
|
||||
},
|
||||
%{
|
||||
"rel" => "http://webfinger.net/rel/profile-page",
|
||||
"type" => "text/html",
|
||||
"href" => user.ap_id
|
||||
},
|
||||
%{"rel" => "salmon", "href" => OStatus.salmon_path(user)},
|
||||
%{"rel" => "magic-public-key", "href" => "data:application/magic-public-key,#{magic_key}"},
|
||||
%{"rel" => "self", "type" => "application/activity+json", "href" => user.ap_id},
|
||||
%{"rel" => "http://ostatus.org/schema/1.0/subscribe", "template" => OStatus.remote_follow_path()}
|
||||
%{
|
||||
"rel" => "http://ostatus.org/schema/1.0/subscribe",
|
||||
"template" => OStatus.remote_follow_path()
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
@ -70,30 +95,42 @@ def represent_user(user, "XML") do
|
|||
{:ok, user} = ensure_keys_present(user)
|
||||
{:ok, _private, public} = Salmon.keys_from_pem(user.info["keys"])
|
||||
magic_key = Salmon.encode_key(public)
|
||||
|
||||
{
|
||||
:XRD, %{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"},
|
||||
:XRD,
|
||||
%{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"},
|
||||
[
|
||||
{:Subject, "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host}"},
|
||||
{:Subject, "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}"},
|
||||
{:Alias, user.ap_id},
|
||||
{:Link, %{rel: "http://schemas.google.com/g/2010#updates-from", type: "application/atom+xml", href: OStatus.feed_path(user)}},
|
||||
{:Link, %{rel: "http://webfinger.net/rel/profile-page", type: "text/html", href: user.ap_id}},
|
||||
{:Link,
|
||||
%{
|
||||
rel: "http://schemas.google.com/g/2010#updates-from",
|
||||
type: "application/atom+xml",
|
||||
href: OStatus.feed_path(user)
|
||||
}},
|
||||
{:Link,
|
||||
%{rel: "http://webfinger.net/rel/profile-page", type: "text/html", href: user.ap_id}},
|
||||
{:Link, %{rel: "salmon", href: OStatus.salmon_path(user)}},
|
||||
{:Link, %{rel: "magic-public-key", href: "data:application/magic-public-key,#{magic_key}"}},
|
||||
{:Link,
|
||||
%{rel: "magic-public-key", href: "data:application/magic-public-key,#{magic_key}"}},
|
||||
{:Link, %{rel: "self", type: "application/activity+json", href: user.ap_id}},
|
||||
{:Link, %{rel: "http://ostatus.org/schema/1.0/subscribe", template: OStatus.remote_follow_path()}}
|
||||
{:Link,
|
||||
%{rel: "http://ostatus.org/schema/1.0/subscribe", template: OStatus.remote_follow_path()}}
|
||||
]
|
||||
}
|
||||
|> XmlBuilder.to_doc
|
||||
|> XmlBuilder.to_doc()
|
||||
end
|
||||
|
||||
# This seems a better fit in Salmon
|
||||
def ensure_keys_present(user) do
|
||||
info = user.info || %{}
|
||||
|
||||
if info["keys"] do
|
||||
{:ok, user}
|
||||
else
|
||||
{:ok, pem} = Salmon.generate_rsa_pem
|
||||
{:ok, pem} = Salmon.generate_rsa_pem()
|
||||
info = Map.put(info, "keys", pem)
|
||||
|
||||
Ecto.Changeset.change(user, info: info)
|
||||
|> User.update_and_set_cache()
|
||||
end
|
||||
|
@ -102,11 +139,28 @@ def ensure_keys_present(user) do
|
|||
defp webfinger_from_xml(doc) do
|
||||
magic_key = XML.string_from_xpath(~s{//Link[@rel="magic-public-key"]/@href}, doc)
|
||||
"data:application/magic-public-key," <> magic_key = magic_key
|
||||
topic = XML.string_from_xpath(~s{//Link[@rel="http://schemas.google.com/g/2010#updates-from"]/@href}, doc)
|
||||
|
||||
topic =
|
||||
XML.string_from_xpath(
|
||||
~s{//Link[@rel="http://schemas.google.com/g/2010#updates-from"]/@href},
|
||||
doc
|
||||
)
|
||||
|
||||
subject = XML.string_from_xpath("//Subject", doc)
|
||||
salmon = XML.string_from_xpath(~s{//Link[@rel="salmon"]/@href}, doc)
|
||||
subscribe_address = XML.string_from_xpath(~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template}, doc)
|
||||
ap_id = XML.string_from_xpath(~s{//Link[@rel="self" and @type="application/activity+json"]/@href}, doc)
|
||||
|
||||
subscribe_address =
|
||||
XML.string_from_xpath(
|
||||
~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template},
|
||||
doc
|
||||
)
|
||||
|
||||
ap_id =
|
||||
XML.string_from_xpath(
|
||||
~s{//Link[@rel="self" and @type="application/activity+json"]/@href},
|
||||
doc
|
||||
)
|
||||
|
||||
data = %{
|
||||
"magic_key" => magic_key,
|
||||
"topic" => topic,
|
||||
|
@ -115,41 +169,51 @@ defp webfinger_from_xml(doc) do
|
|||
"subscribe_address" => subscribe_address,
|
||||
"ap_id" => ap_id
|
||||
}
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
|
||||
defp webfinger_from_json(doc) do
|
||||
data = Enum.reduce(doc["links"], %{"subject" => doc["subject"]}, fn (link, data) ->
|
||||
case {link["type"], link["rel"]} do
|
||||
{"application/activity+json", "self"} ->
|
||||
Map.put(data, "ap_id", link["href"])
|
||||
{_, "magic-public-key"} ->
|
||||
"data:application/magic-public-key," <> magic_key = link["href"]
|
||||
Map.put(data, "magic_key", magic_key)
|
||||
{"application/atom+xml", "http://schemas.google.com/g/2010#updates-from"} ->
|
||||
Map.put(data, "topic", link["href"])
|
||||
{_, "salmon"} ->
|
||||
Map.put(data, "salmon", link["href"])
|
||||
{_, "http://ostatus.org/schema/1.0/subscribe"} ->
|
||||
Map.put(data, "subscribe_address", link["template"])
|
||||
_ ->
|
||||
Logger.debug("Unhandled type: #{inspect(link["type"])}")
|
||||
data
|
||||
end
|
||||
end)
|
||||
data =
|
||||
Enum.reduce(doc["links"], %{"subject" => doc["subject"]}, fn link, data ->
|
||||
case {link["type"], link["rel"]} do
|
||||
{"application/activity+json", "self"} ->
|
||||
Map.put(data, "ap_id", link["href"])
|
||||
|
||||
{_, "magic-public-key"} ->
|
||||
"data:application/magic-public-key," <> magic_key = link["href"]
|
||||
Map.put(data, "magic_key", magic_key)
|
||||
|
||||
{"application/atom+xml", "http://schemas.google.com/g/2010#updates-from"} ->
|
||||
Map.put(data, "topic", link["href"])
|
||||
|
||||
{_, "salmon"} ->
|
||||
Map.put(data, "salmon", link["href"])
|
||||
|
||||
{_, "http://ostatus.org/schema/1.0/subscribe"} ->
|
||||
Map.put(data, "subscribe_address", link["template"])
|
||||
|
||||
_ ->
|
||||
Logger.debug("Unhandled type: #{inspect(link["type"])}")
|
||||
data
|
||||
end
|
||||
end)
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
|
||||
def get_template_from_xml(body) do
|
||||
xpath = "//Link[@rel='lrdd' and @type='application/xrd+xml']/@template"
|
||||
|
||||
with doc when doc != :error <- XML.parse_document(body),
|
||||
template when template != nil <- XML.string_from_xpath(xpath, doc) do
|
||||
template when template != nil <- XML.string_from_xpath(xpath, doc) do
|
||||
{:ok, template}
|
||||
end
|
||||
end
|
||||
|
||||
def find_lrdd_template(domain) do
|
||||
with {:ok, %{status_code: status_code, body: body}} when status_code in 200..299 <- @httpoison.get("http://#{domain}/.well-known/host-meta", [], follow_redirect: true) do
|
||||
with {:ok, %{status_code: status_code, body: body}} when status_code in 200..299 <-
|
||||
@httpoison.get("http://#{domain}/.well-known/host-meta", [], follow_redirect: true) do
|
||||
get_template_from_xml(body)
|
||||
else
|
||||
_ ->
|
||||
|
@ -163,28 +227,38 @@ def find_lrdd_template(domain) do
|
|||
|
||||
def finger(account) do
|
||||
account = String.trim_leading(account, "@")
|
||||
domain = with [_name, domain] <- String.split(account, "@") do
|
||||
domain
|
||||
else _e ->
|
||||
URI.parse(account).host
|
||||
end
|
||||
|
||||
domain =
|
||||
with [_name, domain] <- String.split(account, "@") do
|
||||
domain
|
||||
else
|
||||
_e ->
|
||||
URI.parse(account).host
|
||||
end
|
||||
|
||||
case find_lrdd_template(domain) do
|
||||
{:ok, template} ->
|
||||
address = String.replace(template, "{uri}", URI.encode(account))
|
||||
|
||||
_ ->
|
||||
address = "http://#{domain}/.well-known/webfinger?resource=acct:#{account}"
|
||||
end
|
||||
|
||||
with response <- @httpoison.get(address, ["Accept": "application/xrd+xml,application/jrd+json"], follow_redirect: true),
|
||||
with response <-
|
||||
@httpoison.get(
|
||||
address,
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
),
|
||||
{:ok, %{status_code: status_code, body: body}} when status_code in 200..299 <- response do
|
||||
doc = XML.parse_document(body)
|
||||
if doc != :error do
|
||||
webfinger_from_xml(doc)
|
||||
else
|
||||
{:ok, doc} = Jason.decode(body)
|
||||
webfinger_from_json(doc)
|
||||
end
|
||||
doc = XML.parse_document(body)
|
||||
|
||||
if doc != :error do
|
||||
webfinger_from_xml(doc)
|
||||
else
|
||||
{:ok, doc} = Jason.decode(body)
|
||||
webfinger_from_json(doc)
|
||||
end
|
||||
else
|
||||
e ->
|
||||
Logger.debug(fn -> "Couldn't finger #{account}" end)
|
||||
|
|
|
@ -4,7 +4,7 @@ defmodule Pleroma.Web.WebFinger.WebFingerController do
|
|||
alias Pleroma.Web.WebFinger
|
||||
|
||||
def host_meta(conn, _params) do
|
||||
xml = WebFinger.host_meta
|
||||
xml = WebFinger.host_meta()
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/xrd+xml")
|
||||
|
@ -21,12 +21,14 @@ def webfinger(conn, %{"resource" => resource}) do
|
|||
else
|
||||
_e -> send_resp(conn, 404, "Couldn't find user")
|
||||
end
|
||||
|
||||
n when n in ["json", "jrd+json"] ->
|
||||
with {:ok, response} <- WebFinger.webfinger(resource, "JSON") do
|
||||
json(conn, response)
|
||||
else
|
||||
_e -> send_resp(conn, 404, "Couldn't find user")
|
||||
end
|
||||
|
||||
_ ->
|
||||
send_resp(conn, 404, "Unsupported format")
|
||||
end
|
||||
|
|
|
@ -26,15 +26,16 @@ def verify(subscription, getter \\ &@httpoison.get/3) do
|
|||
url = hd(String.split(subscription.callback, "?"))
|
||||
query = URI.parse(subscription.callback).query || ""
|
||||
params = Map.merge(params, URI.decode_query(query))
|
||||
with {:ok, response} <- getter.(url, [], [params: params]),
|
||||
^challenge <- response.body
|
||||
do
|
||||
|
||||
with {:ok, response} <- getter.(url, [], params: params),
|
||||
^challenge <- response.body do
|
||||
changeset = Changeset.change(subscription, %{state: "active"})
|
||||
Repo.update(changeset)
|
||||
else e ->
|
||||
Logger.debug("Couldn't verify subscription")
|
||||
Logger.debug(inspect(e))
|
||||
{:error, subscription}
|
||||
else
|
||||
e ->
|
||||
Logger.debug("Couldn't verify subscription")
|
||||
Logger.debug(inspect(e))
|
||||
{:error, subscription}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -46,17 +47,24 @@ def verify(subscription, getter \\ &@httpoison.get/3) do
|
|||
"Undo",
|
||||
"Delete"
|
||||
]
|
||||
def publish(topic, user, %{data: %{"type" => type}} = activity) when type in @supported_activities do
|
||||
def publish(topic, user, %{data: %{"type" => type}} = activity)
|
||||
when type in @supported_activities do
|
||||
# TODO: Only send to still valid subscriptions.
|
||||
query = from sub in WebsubServerSubscription,
|
||||
where: sub.topic == ^topic and sub.state == "active",
|
||||
where: fragment("? > NOW()", sub.valid_until)
|
||||
query =
|
||||
from(
|
||||
sub in WebsubServerSubscription,
|
||||
where: sub.topic == ^topic and sub.state == "active",
|
||||
where: fragment("? > NOW()", sub.valid_until)
|
||||
)
|
||||
|
||||
subscriptions = Repo.all(query)
|
||||
Enum.each(subscriptions, fn(sub) ->
|
||||
response = user
|
||||
|> FeedRepresenter.to_simple_form([activity], [user])
|
||||
|> :xmerl.export_simple(:xmerl_xml)
|
||||
|> to_string
|
||||
|
||||
Enum.each(subscriptions, fn sub ->
|
||||
response =
|
||||
user
|
||||
|> FeedRepresenter.to_simple_form([activity], [user])
|
||||
|> :xmerl.export_simple(:xmerl_xml)
|
||||
|> to_string
|
||||
|
||||
data = %{
|
||||
xml: response,
|
||||
|
@ -64,22 +72,24 @@ def publish(topic, user, %{data: %{"type" => type}} = activity) when type in @su
|
|||
callback: sub.callback,
|
||||
secret: sub.secret
|
||||
}
|
||||
|
||||
Pleroma.Web.Federator.enqueue(:publish_single_websub, data)
|
||||
end)
|
||||
end
|
||||
def publish(_,_,_), do: ""
|
||||
|
||||
def publish(_, _, _), do: ""
|
||||
|
||||
def sign(secret, doc) do
|
||||
:crypto.hmac(:sha, secret, to_string(doc)) |> Base.encode16 |> String.downcase
|
||||
:crypto.hmac(:sha, secret, to_string(doc)) |> Base.encode16() |> String.downcase()
|
||||
end
|
||||
|
||||
def incoming_subscription_request(user, %{"hub.mode" => "subscribe"} = params) do
|
||||
with {:ok, topic} <- valid_topic(params, user),
|
||||
{:ok, lease_time} <- lease_time(params),
|
||||
secret <- params["hub.secret"],
|
||||
callback <- params["hub.callback"]
|
||||
do
|
||||
callback <- params["hub.callback"] do
|
||||
subscription = get_subscription(topic, callback)
|
||||
|
||||
data = %{
|
||||
state: subscription.state || "requested",
|
||||
topic: topic,
|
||||
|
@ -90,18 +100,20 @@ def incoming_subscription_request(user, %{"hub.mode" => "subscribe"} = params) d
|
|||
change = Changeset.change(subscription, data)
|
||||
websub = Repo.insert_or_update!(change)
|
||||
|
||||
change = Changeset.change(websub, %{valid_until:
|
||||
NaiveDateTime.add(websub.updated_at, lease_time)})
|
||||
change =
|
||||
Changeset.change(websub, %{valid_until: NaiveDateTime.add(websub.updated_at, lease_time)})
|
||||
|
||||
websub = Repo.update!(change)
|
||||
|
||||
Pleroma.Web.Federator.enqueue(:verify_websub, websub)
|
||||
|
||||
{:ok, websub}
|
||||
else {:error, reason} ->
|
||||
Logger.debug("Couldn't create subscription")
|
||||
Logger.debug(inspect(reason))
|
||||
else
|
||||
{:error, reason} ->
|
||||
Logger.debug("Couldn't create subscription")
|
||||
Logger.debug(inspect(reason))
|
||||
|
||||
{:error, reason}
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -112,7 +124,8 @@ defp get_subscription(topic, callback) do
|
|||
|
||||
# Temp hack for mastodon.
|
||||
defp lease_time(%{"hub.lease_seconds" => ""}) do
|
||||
{:ok, 60 * 60 * 24 * 3} # three days
|
||||
# three days
|
||||
{:ok, 60 * 60 * 24 * 3}
|
||||
end
|
||||
|
||||
defp lease_time(%{"hub.lease_seconds" => lease_seconds}) do
|
||||
|
@ -120,7 +133,8 @@ defp lease_time(%{"hub.lease_seconds" => lease_seconds}) do
|
|||
end
|
||||
|
||||
defp lease_time(_) do
|
||||
{:ok, 60 * 60 * 24 * 3} # three days
|
||||
# three days
|
||||
{:ok, 60 * 60 * 24 * 3}
|
||||
end
|
||||
|
||||
defp valid_topic(%{"hub.topic" => topic}, user) do
|
||||
|
@ -134,21 +148,26 @@ defp valid_topic(%{"hub.topic" => topic}, user) do
|
|||
def subscribe(subscriber, subscribed, requester \\ &request_subscription/1) do
|
||||
topic = subscribed.info["topic"]
|
||||
# FIXME: Race condition, use transactions
|
||||
{:ok, subscription} = with subscription when not is_nil(subscription) <- Repo.get_by(WebsubClientSubscription, topic: topic) do
|
||||
subscribers = [subscriber.ap_id | subscription.subscribers] |> Enum.uniq
|
||||
change = Ecto.Changeset.change(subscription, %{subscribers: subscribers})
|
||||
Repo.update(change)
|
||||
else _e ->
|
||||
subscription = %WebsubClientSubscription{
|
||||
topic: topic,
|
||||
hub: subscribed.info["hub"],
|
||||
subscribers: [subscriber.ap_id],
|
||||
state: "requested",
|
||||
secret: :crypto.strong_rand_bytes(8) |> Base.url_encode64,
|
||||
user: subscribed
|
||||
}
|
||||
Repo.insert(subscription)
|
||||
end
|
||||
{:ok, subscription} =
|
||||
with subscription when not is_nil(subscription) <-
|
||||
Repo.get_by(WebsubClientSubscription, topic: topic) do
|
||||
subscribers = [subscriber.ap_id | subscription.subscribers] |> Enum.uniq()
|
||||
change = Ecto.Changeset.change(subscription, %{subscribers: subscribers})
|
||||
Repo.update(change)
|
||||
else
|
||||
_e ->
|
||||
subscription = %WebsubClientSubscription{
|
||||
topic: topic,
|
||||
hub: subscribed.info["hub"],
|
||||
subscribers: [subscriber.ap_id],
|
||||
state: "requested",
|
||||
secret: :crypto.strong_rand_bytes(8) |> Base.url_encode64(),
|
||||
user: subscribed
|
||||
}
|
||||
|
||||
Repo.insert(subscription)
|
||||
end
|
||||
|
||||
requester.(subscription)
|
||||
end
|
||||
|
||||
|
@ -159,24 +178,25 @@ def gather_feed_data(topic, getter \\ &@httpoison.get/1) do
|
|||
doc <- XML.parse_document(body),
|
||||
uri when not is_nil(uri) <- XML.string_from_xpath("/feed/author[1]/uri", doc),
|
||||
hub when not is_nil(hub) <- XML.string_from_xpath(~S{/feed/link[@rel="hub"]/@href}, doc) do
|
||||
|
||||
name = XML.string_from_xpath("/feed/author[1]/name", doc)
|
||||
preferredUsername = XML.string_from_xpath("/feed/author[1]/poco:preferredUsername", doc)
|
||||
displayName = XML.string_from_xpath("/feed/author[1]/poco:displayName", doc)
|
||||
avatar = OStatus.make_avatar_object(doc)
|
||||
bio = XML.string_from_xpath("/feed/author[1]/summary", doc)
|
||||
|
||||
{:ok, %{
|
||||
"uri" => uri,
|
||||
"hub" => hub,
|
||||
"nickname" => preferredUsername || name,
|
||||
"name" => displayName || name,
|
||||
"host" => URI.parse(uri).host,
|
||||
"avatar" => avatar,
|
||||
"bio" => bio
|
||||
}}
|
||||
else e ->
|
||||
{:error, e}
|
||||
{:ok,
|
||||
%{
|
||||
"uri" => uri,
|
||||
"hub" => hub,
|
||||
"nickname" => preferredUsername || name,
|
||||
"name" => displayName || name,
|
||||
"host" => URI.parse(uri).host,
|
||||
"avatar" => avatar,
|
||||
"bio" => bio
|
||||
}}
|
||||
else
|
||||
e ->
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -190,43 +210,45 @@ def request_subscription(websub, poster \\ &@httpoison.post/3, timeout \\ 10_000
|
|||
|
||||
# This checks once a second if we are confirmed yet
|
||||
websub_checker = fn ->
|
||||
helper = fn (helper) ->
|
||||
helper = fn helper ->
|
||||
:timer.sleep(1000)
|
||||
websub = Repo.get_by(WebsubClientSubscription, id: websub.id, state: "accepted")
|
||||
if websub, do: websub, else: helper.(helper)
|
||||
end
|
||||
|
||||
helper.(helper)
|
||||
end
|
||||
|
||||
task = Task.async(websub_checker)
|
||||
|
||||
with {:ok, %{status_code: 202}} <- poster.(websub.hub, {:form, data}, ["Content-type": "application/x-www-form-urlencoded"]),
|
||||
with {:ok, %{status_code: 202}} <-
|
||||
poster.(websub.hub, {:form, data}, "Content-type": "application/x-www-form-urlencoded"),
|
||||
{:ok, websub} <- Task.yield(task, timeout) do
|
||||
{:ok, websub}
|
||||
else e ->
|
||||
Task.shutdown(task)
|
||||
else
|
||||
e ->
|
||||
Task.shutdown(task)
|
||||
|
||||
change = Ecto.Changeset.change(websub, %{state: "rejected"})
|
||||
{:ok, websub} = Repo.update(change)
|
||||
change = Ecto.Changeset.change(websub, %{state: "rejected"})
|
||||
{:ok, websub} = Repo.update(change)
|
||||
|
||||
Logger.debug(fn -> "Couldn't confirm subscription: #{inspect(websub)}" end)
|
||||
Logger.debug(fn -> "error: #{inspect(e)}" end)
|
||||
Logger.debug(fn -> "Couldn't confirm subscription: #{inspect(websub)}" end)
|
||||
Logger.debug(fn -> "error: #{inspect(e)}" end)
|
||||
|
||||
{:error, websub}
|
||||
{:error, websub}
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_subscriptions(delta \\ 60 * 60 * 24) do
|
||||
Logger.debug("Refreshing subscriptions")
|
||||
|
||||
cut_off = NaiveDateTime.add(NaiveDateTime.utc_now, delta)
|
||||
cut_off = NaiveDateTime.add(NaiveDateTime.utc_now(), delta)
|
||||
|
||||
query = from sub in WebsubClientSubscription,
|
||||
where: sub.valid_until < ^cut_off
|
||||
query = from(sub in WebsubClientSubscription, where: sub.valid_until < ^cut_off)
|
||||
|
||||
subs = Repo.all(query)
|
||||
|
||||
Enum.each(subs, fn (sub) ->
|
||||
Enum.each(subs, fn sub ->
|
||||
Pleroma.Web.Federator.enqueue(:request_subscription, sub)
|
||||
end)
|
||||
end
|
||||
|
|
|
@ -3,13 +3,13 @@ defmodule Pleroma.Web.Websub.WebsubClientSubscription do
|
|||
alias Pleroma.User
|
||||
|
||||
schema "websub_client_subscriptions" do
|
||||
field :topic, :string
|
||||
field :secret, :string
|
||||
field :valid_until, :naive_datetime
|
||||
field :state, :string
|
||||
field :subscribers, {:array, :string}, default: []
|
||||
field :hub, :string
|
||||
belongs_to :user, User
|
||||
field(:topic, :string)
|
||||
field(:secret, :string)
|
||||
field(:valid_until, :naive_datetime)
|
||||
field(:state, :string)
|
||||
field(:subscribers, {:array, :string}, default: [])
|
||||
field(:hub, :string)
|
||||
belongs_to(:user, User)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
|
|
@ -8,36 +8,49 @@ defmodule Pleroma.Web.Websub.WebsubController do
|
|||
def websub_subscription_request(conn, %{"nickname" => nickname} = params) do
|
||||
user = User.get_cached_by_nickname(nickname)
|
||||
|
||||
with {:ok, _websub} <- Websub.incoming_subscription_request(user, params)
|
||||
do
|
||||
with {:ok, _websub} <- Websub.incoming_subscription_request(user, params) do
|
||||
conn
|
||||
|> send_resp(202, "Accepted")
|
||||
else {:error, reason} ->
|
||||
conn
|
||||
|> send_resp(500, reason)
|
||||
else
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> send_resp(500, reason)
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Extract this into the Websub module
|
||||
def websub_subscription_confirmation(conn, %{"id" => id, "hub.mode" => "subscribe", "hub.challenge" => challenge, "hub.topic" => topic} = params) do
|
||||
def websub_subscription_confirmation(
|
||||
conn,
|
||||
%{
|
||||
"id" => id,
|
||||
"hub.mode" => "subscribe",
|
||||
"hub.challenge" => challenge,
|
||||
"hub.topic" => topic
|
||||
} = params
|
||||
) do
|
||||
Logger.debug("Got WebSub confirmation")
|
||||
Logger.debug(inspect(params))
|
||||
lease_seconds = if params["hub.lease_seconds"] do
|
||||
String.to_integer(params["hub.lease_seconds"])
|
||||
else
|
||||
# Guess 3 days
|
||||
60 * 60 * 24 * 3
|
||||
end
|
||||
|
||||
with %WebsubClientSubscription{} = websub <- Repo.get_by(WebsubClientSubscription, id: id, topic: topic) do
|
||||
valid_until = NaiveDateTime.add(NaiveDateTime.utc_now, lease_seconds)
|
||||
lease_seconds =
|
||||
if params["hub.lease_seconds"] do
|
||||
String.to_integer(params["hub.lease_seconds"])
|
||||
else
|
||||
# Guess 3 days
|
||||
60 * 60 * 24 * 3
|
||||
end
|
||||
|
||||
with %WebsubClientSubscription{} = websub <-
|
||||
Repo.get_by(WebsubClientSubscription, id: id, topic: topic) do
|
||||
valid_until = NaiveDateTime.add(NaiveDateTime.utc_now(), lease_seconds)
|
||||
change = Ecto.Changeset.change(websub, %{state: "accepted", valid_until: valid_until})
|
||||
{:ok, _websub} = Repo.update(change)
|
||||
|
||||
conn
|
||||
|> send_resp(200, challenge)
|
||||
else _e ->
|
||||
conn
|
||||
|> send_resp(500, "Error")
|
||||
else
|
||||
_e ->
|
||||
conn
|
||||
|> send_resp(500, "Error")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -48,12 +61,15 @@ def websub_incoming(conn, %{"id" => id}) do
|
|||
{:ok, body, _conn} = read_body(conn),
|
||||
^signature <- Websub.sign(websub.secret, body) do
|
||||
Federator.enqueue(:incoming_doc, body)
|
||||
|
||||
conn
|
||||
|> send_resp(200, "OK")
|
||||
else _e ->
|
||||
Logger.debug("Can't handle incoming subscription post")
|
||||
conn
|
||||
|> send_resp(500, "Error")
|
||||
else
|
||||
_e ->
|
||||
Logger.debug("Can't handle incoming subscription post")
|
||||
|
||||
conn
|
||||
|> send_resp(500, "Error")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,11 +2,11 @@ defmodule Pleroma.Web.Websub.WebsubServerSubscription do
|
|||
use Ecto.Schema
|
||||
|
||||
schema "websub_server_subscriptions" do
|
||||
field :topic, :string
|
||||
field :callback, :string
|
||||
field :secret, :string
|
||||
field :valid_until, :naive_datetime
|
||||
field :state, :string
|
||||
field(:topic, :string)
|
||||
field(:callback, :string)
|
||||
field(:secret, :string)
|
||||
field(:valid_until, :naive_datetime)
|
||||
field(:state, :string)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
|
|
@ -2,21 +2,24 @@ defmodule Pleroma.Web.XML do
|
|||
require Logger
|
||||
|
||||
def string_from_xpath(_, :error), do: nil
|
||||
|
||||
def string_from_xpath(xpath, doc) do
|
||||
{:xmlObj, :string, res} = :xmerl_xpath.string('string(#{xpath})', doc)
|
||||
|
||||
res = res
|
||||
|> to_string
|
||||
|> String.trim
|
||||
res =
|
||||
res
|
||||
|> to_string
|
||||
|> String.trim()
|
||||
|
||||
if res == "", do: nil, else: res
|
||||
end
|
||||
|
||||
def parse_document(text) do
|
||||
try do
|
||||
{doc, _rest} = text
|
||||
|> :binary.bin_to_list
|
||||
|> :xmerl_scan.string
|
||||
{doc, _rest} =
|
||||
text
|
||||
|> :binary.bin_to_list()
|
||||
|> :xmerl_scan.string()
|
||||
|
||||
doc
|
||||
catch
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
defmodule Phoenix.Transports.WebSocket.Raw do
|
||||
import Plug.Conn, only: [
|
||||
fetch_query_params: 1,
|
||||
send_resp: 3
|
||||
]
|
||||
import Plug.Conn,
|
||||
only: [
|
||||
fetch_query_params: 1,
|
||||
send_resp: 3
|
||||
]
|
||||
|
||||
alias Phoenix.Socket.Transport
|
||||
|
||||
def default_config do
|
||||
|
@ -16,21 +18,24 @@ def default_config do
|
|||
def init(%Plug.Conn{method: "GET"} = conn, {endpoint, handler, transport}) do
|
||||
{_, opts} = handler.__transport__(transport)
|
||||
|
||||
conn = conn
|
||||
|> fetch_query_params
|
||||
|> Transport.transport_log(opts[:transport_log])
|
||||
|> Transport.force_ssl(handler, endpoint, opts)
|
||||
|> Transport.check_origin(handler, endpoint, opts)
|
||||
conn =
|
||||
conn
|
||||
|> fetch_query_params
|
||||
|> Transport.transport_log(opts[:transport_log])
|
||||
|> Transport.force_ssl(handler, endpoint, opts)
|
||||
|> Transport.check_origin(handler, endpoint, opts)
|
||||
|
||||
case conn do
|
||||
%{halted: false} = conn ->
|
||||
case Transport.connect(endpoint, handler, transport, __MODULE__, nil, conn.params) do
|
||||
{:ok, socket} ->
|
||||
{:ok, conn, {__MODULE__, {socket, opts}}}
|
||||
|
||||
:error ->
|
||||
send_resp(conn, :forbidden, "")
|
||||
{:error, conn}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, conn}
|
||||
end
|
||||
|
@ -52,16 +57,19 @@ def ws_handle(op, data, state) do
|
|||
|> case do
|
||||
{op, data} ->
|
||||
{:reply, {op, data}, state}
|
||||
|
||||
{op, data, state} ->
|
||||
{:reply, {op, data}, state}
|
||||
|
||||
%{} = state ->
|
||||
{:ok, state}
|
||||
|
||||
_ ->
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
|
||||
def ws_info({_,_} = tuple, state) do
|
||||
def ws_info({_, _} = tuple, state) do
|
||||
{:reply, tuple, state}
|
||||
end
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ def to_xml(content) when is_list(content) do
|
|||
for element <- content do
|
||||
to_xml(element)
|
||||
end
|
||||
|> Enum.join
|
||||
|> Enum.join()
|
||||
end
|
||||
|
||||
def to_xml(%NaiveDateTime{} = time) do
|
||||
|
@ -33,10 +33,12 @@ def to_xml(%NaiveDateTime{} = time) do
|
|||
def to_doc(content), do: ~s(<?xml version="1.0" encoding="UTF-8"?>) <> to_xml(content)
|
||||
|
||||
defp make_open_tag(tag, attributes) do
|
||||
attributes_string = for {attribute, value} <- attributes do
|
||||
"#{attribute}=\"#{value}\""
|
||||
end |> Enum.join(" ")
|
||||
attributes_string =
|
||||
for {attribute, value} <- attributes do
|
||||
"#{attribute}=\"#{value}\""
|
||||
end
|
||||
|> Enum.join(" ")
|
||||
|
||||
[tag, attributes_string] |> Enum.join(" ") |> String.trim
|
||||
[tag, attributes_string] |> Enum.join(" ") |> String.trim()
|
||||
end
|
||||
end
|
||||
|
|
65
mix.exs
65
mix.exs
|
@ -2,48 +2,51 @@ defmodule Pleroma.Mixfile do
|
|||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[app: :pleroma,
|
||||
version: "0.9.0",
|
||||
elixir: "~> 1.4",
|
||||
elixirc_paths: elixirc_paths(Mix.env),
|
||||
compilers: [:phoenix, :gettext] ++ Mix.compilers,
|
||||
start_permanent: Mix.env == :prod,
|
||||
aliases: aliases(),
|
||||
deps: deps()]
|
||||
[
|
||||
app: :pleroma,
|
||||
version: "0.9.0",
|
||||
elixir: "~> 1.4",
|
||||
elixirc_paths: elixirc_paths(Mix.env()),
|
||||
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
|
||||
start_permanent: Mix.env() == :prod,
|
||||
aliases: aliases(),
|
||||
deps: deps()
|
||||
]
|
||||
end
|
||||
|
||||
# Configuration for the OTP application.
|
||||
#
|
||||
# Type `mix help compile.app` for more information.
|
||||
def application do
|
||||
[mod: {Pleroma.Application, []},
|
||||
extra_applications: [:logger, :runtime_tools, :comeonin]]
|
||||
[mod: {Pleroma.Application, []}, extra_applications: [:logger, :runtime_tools, :comeonin]]
|
||||
end
|
||||
|
||||
# Specifies which paths to compile per environment.
|
||||
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
||||
defp elixirc_paths(_), do: ["lib"]
|
||||
defp elixirc_paths(_), do: ["lib"]
|
||||
|
||||
# Specifies your project dependencies.
|
||||
#
|
||||
# Type `mix help deps` for examples and options.
|
||||
defp deps do
|
||||
[{:phoenix, "~> 1.3.0"},
|
||||
{:phoenix_pubsub, "~> 1.0"},
|
||||
{:phoenix_ecto, "~> 3.2"},
|
||||
{:postgrex, ">= 0.0.0"},
|
||||
{:gettext, "~> 0.11"},
|
||||
{:cowboy, "~> 1.0", override: true},
|
||||
{:comeonin, "~> 3.0"},
|
||||
{:trailing_format_plug, "~> 0.0.5" },
|
||||
{:html_sanitize_ex, "~> 1.3.0-rc1"},
|
||||
{:phoenix_html, "~> 2.10"},
|
||||
{:calendar, "~> 0.16.1"},
|
||||
{:cachex, "~> 2.1"},
|
||||
{:httpoison, "~> 0.11.2"},
|
||||
{:jason, "~> 1.0"},
|
||||
{:ex_machina, "~> 2.0", only: :test},
|
||||
{:credo, "~> 0.7", only: [:dev, :test]}]
|
||||
[
|
||||
{:phoenix, "~> 1.3.0"},
|
||||
{:phoenix_pubsub, "~> 1.0"},
|
||||
{:phoenix_ecto, "~> 3.2"},
|
||||
{:postgrex, ">= 0.0.0"},
|
||||
{:gettext, "~> 0.11"},
|
||||
{:cowboy, "~> 1.0", override: true},
|
||||
{:comeonin, "~> 3.0"},
|
||||
{:trailing_format_plug, "~> 0.0.5"},
|
||||
{:html_sanitize_ex, "~> 1.3.0-rc1"},
|
||||
{:phoenix_html, "~> 2.10"},
|
||||
{:calendar, "~> 0.16.1"},
|
||||
{:cachex, "~> 2.1"},
|
||||
{:httpoison, "~> 0.11.2"},
|
||||
{:jason, "~> 1.0"},
|
||||
{:ex_machina, "~> 2.0", only: :test},
|
||||
{:credo, "~> 0.7", only: [:dev, :test]}
|
||||
]
|
||||
end
|
||||
|
||||
# Aliases are shortcuts or tasks specific to the current project.
|
||||
|
@ -53,8 +56,10 @@ defp deps do
|
|||
#
|
||||
# See the documentation for `Mix` for more info on aliases.
|
||||
defp aliases do
|
||||
["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
|
||||
"ecto.reset": ["ecto.drop", "ecto.setup"],
|
||||
"test": ["ecto.create --quiet", "ecto.migrate", "test"]]
|
||||
[
|
||||
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
|
||||
"ecto.reset": ["ecto.drop", "ecto.setup"],
|
||||
test: ["ecto.create --quiet", "ecto.migrate", "test"]
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,7 +18,9 @@ test "returns activities by it's objects AP ids" do
|
|||
|
||||
test "returns the activity that created an object" do
|
||||
activity = insert(:note_activity)
|
||||
found_activity = Pleroma.Activity.get_create_activity_by_object_ap_id(activity.data["object"]["id"])
|
||||
|
||||
found_activity =
|
||||
Pleroma.Activity.get_create_activity_by_object_ap_id(activity.data["object"]["id"])
|
||||
|
||||
assert activity == found_activity
|
||||
end
|
||||
|
|
|
@ -7,44 +7,56 @@ defmodule Pleroma.FormatterTest do
|
|||
describe ".add_hashtag_links" do
|
||||
test "turns hashtags into links" do
|
||||
text = "I love #cofe and #2hu"
|
||||
expected_text = "I love <a href='http://localhost:4001/tag/cofe' rel='tag'>#cofe</a> and <a href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a>"
|
||||
|
||||
expected_text =
|
||||
"I love <a href='http://localhost:4001/tag/cofe' rel='tag'>#cofe</a> and <a href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a>"
|
||||
|
||||
tags = Formatter.parse_tags(text)
|
||||
assert expected_text == Formatter.add_hashtag_links({[], text}, tags) |> Formatter.finalize
|
||||
|
||||
assert expected_text ==
|
||||
Formatter.add_hashtag_links({[], text}, tags) |> Formatter.finalize()
|
||||
end
|
||||
end
|
||||
|
||||
describe ".add_links" do
|
||||
test "turning urls into links" do
|
||||
text = "Hey, check out https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla."
|
||||
expected = "Hey, check out <a href='https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla'>https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla</a>."
|
||||
|
||||
assert Formatter.add_links({[], text}) |> Formatter.finalize == expected
|
||||
expected =
|
||||
"Hey, check out <a href='https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla'>https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla</a>."
|
||||
|
||||
assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
|
||||
|
||||
text = "https://mastodon.social/@lambadalambda"
|
||||
expected = "<a href='https://mastodon.social/@lambadalambda'>https://mastodon.social/@lambadalambda</a>"
|
||||
|
||||
assert Formatter.add_links({[], text}) |> Formatter.finalize == expected
|
||||
expected =
|
||||
"<a href='https://mastodon.social/@lambadalambda'>https://mastodon.social/@lambadalambda</a>"
|
||||
|
||||
assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
|
||||
|
||||
text = "@lambadalambda"
|
||||
expected = "@lambadalambda"
|
||||
|
||||
assert Formatter.add_links({[], text}) |> Formatter.finalize == expected
|
||||
assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
|
||||
|
||||
text = "http://www.cs.vu.nl/~ast/intel/"
|
||||
expected = "<a href='http://www.cs.vu.nl/~ast/intel/'>http://www.cs.vu.nl/~ast/intel/</a>"
|
||||
|
||||
assert Formatter.add_links({[], text}) |> Formatter.finalize == expected
|
||||
assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
|
||||
|
||||
text = "https://forum.zdoom.org/viewtopic.php?f=44&t=57087"
|
||||
expected = "<a href='https://forum.zdoom.org/viewtopic.php?f=44&t=57087'>https://forum.zdoom.org/viewtopic.php?f=44&t=57087</a>"
|
||||
|
||||
assert Formatter.add_links({[], text}) |> Formatter.finalize == expected
|
||||
expected =
|
||||
"<a href='https://forum.zdoom.org/viewtopic.php?f=44&t=57087'>https://forum.zdoom.org/viewtopic.php?f=44&t=57087</a>"
|
||||
|
||||
assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
|
||||
|
||||
text = "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul"
|
||||
expected = "<a href='https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul'>https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul</a>"
|
||||
|
||||
assert Formatter.add_links({[], text}) |> Formatter.finalize == expected
|
||||
expected =
|
||||
"<a href='https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul'>https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul</a>"
|
||||
|
||||
assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -60,9 +72,14 @@ test "gives a replacement for user links" do
|
|||
{subs, text} = Formatter.add_user_links({[], text}, mentions)
|
||||
|
||||
assert length(subs) == 3
|
||||
Enum.each(subs, fn({uuid, _}) -> assert String.contains?(text, uuid) end)
|
||||
Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end)
|
||||
|
||||
expected_text = "<span><a href='#{gsimg.ap_id}'>@<span>gsimg</span></a></span> According to <span><a href='#{archaeme.ap_id}'>@<span>archaeme</span></a></span>, that is @daggsy. Also hello <span><a href='#{archaeme_remote.ap_id}'>@<span>archaeme</span></a></span>"
|
||||
expected_text =
|
||||
"<span><a href='#{gsimg.ap_id}'>@<span>gsimg</span></a></span> According to <span><a href='#{
|
||||
archaeme.ap_id
|
||||
}'>@<span>archaeme</span></a></span>, that is @daggsy. Also hello <span><a href='#{
|
||||
archaeme_remote.ap_id
|
||||
}'>@<span>archaeme</span></a></span>"
|
||||
|
||||
assert expected_text == Formatter.finalize({subs, text})
|
||||
end
|
||||
|
@ -71,6 +88,7 @@ test "gives a replacement for user links" do
|
|||
describe ".parse_tags" do
|
||||
test "parses tags in the text" do
|
||||
text = "Here's a #Test. Maybe these are #working or not. What about #漢字? And #は。"
|
||||
|
||||
expected = [
|
||||
{"#Test", "test"},
|
||||
{"#working", "working"},
|
||||
|
@ -92,7 +110,7 @@ test "it can parse mentions and return the relevant users" do
|
|||
expected_result = [
|
||||
{"@gsimg", gsimg},
|
||||
{"@archaeme", archaeme},
|
||||
{"@archaeme@archae.me", archaeme_remote},
|
||||
{"@archaeme@archae.me", archaeme_remote}
|
||||
]
|
||||
|
||||
assert Formatter.parse_mentions(text) == expected_result
|
||||
|
@ -101,7 +119,8 @@ test "it can parse mentions and return the relevant users" do
|
|||
test "it adds cool emoji" do
|
||||
text = "I love :moominmamma:"
|
||||
|
||||
expected_result = "I love <img height='32px' width='32px' alt='moominmamma' title='moominmamma' src='/finmoji/128px/moominmamma-128.png' />"
|
||||
expected_result =
|
||||
"I love <img height='32px' width='32px' alt='moominmamma' title='moominmamma' src='/finmoji/128px/moominmamma-128.png' />"
|
||||
|
||||
assert Formatter.emojify(text) == expected_result
|
||||
end
|
||||
|
|
|
@ -10,7 +10,10 @@ test "notifies someone when they are directly addressed" do
|
|||
other_user = insert(:user)
|
||||
third_user = insert(:user)
|
||||
|
||||
{:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname} and @#{third_user.nickname}"})
|
||||
{:ok, activity} =
|
||||
TwitterAPI.create_status(user, %{
|
||||
"status" => "hey @#{other_user.nickname} and @#{third_user.nickname}"
|
||||
})
|
||||
|
||||
{:ok, [notification, other_notification]} = Notification.create_notifications(activity)
|
||||
|
||||
|
@ -37,7 +40,9 @@ test "it gets a notification that belongs to the user" do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
|
||||
{:ok, activity} =
|
||||
TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
|
||||
|
||||
{:ok, [notification]} = Notification.create_notifications(activity)
|
||||
{:ok, notification} = Notification.get(other_user, notification.id)
|
||||
|
||||
|
@ -48,7 +53,9 @@ test "it returns error if the notification doesn't belong to the user" do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
|
||||
{:ok, activity} =
|
||||
TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
|
||||
|
||||
{:ok, [notification]} = Notification.create_notifications(activity)
|
||||
{:error, _notification} = Notification.get(user, notification.id)
|
||||
end
|
||||
|
@ -59,7 +66,9 @@ test "it dismisses a notification that belongs to the user" do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
|
||||
{:ok, activity} =
|
||||
TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
|
||||
|
||||
{:ok, [notification]} = Notification.create_notifications(activity)
|
||||
{:ok, notification} = Notification.dismiss(other_user, notification.id)
|
||||
|
||||
|
@ -70,7 +79,9 @@ test "it returns error if the notification doesn't belong to the user" do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
|
||||
{:ok, activity} =
|
||||
TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
|
||||
|
||||
{:ok, [notification]} = Notification.create_notifications(activity)
|
||||
{:error, _notification} = Notification.dismiss(user, notification.id)
|
||||
end
|
||||
|
@ -82,9 +93,18 @@ test "it clears all notifications belonging to the user" do
|
|||
other_user = insert(:user)
|
||||
third_user = insert(:user)
|
||||
|
||||
{:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname} and @#{third_user.nickname} !"})
|
||||
{:ok, activity} =
|
||||
TwitterAPI.create_status(user, %{
|
||||
"status" => "hey @#{other_user.nickname} and @#{third_user.nickname} !"
|
||||
})
|
||||
|
||||
{:ok, _notifs} = Notification.create_notifications(activity)
|
||||
{:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey again @#{other_user.nickname} and @#{third_user.nickname} !"})
|
||||
|
||||
{:ok, activity} =
|
||||
TwitterAPI.create_status(user, %{
|
||||
"status" => "hey again @#{other_user.nickname} and @#{third_user.nickname} !"
|
||||
})
|
||||
|
||||
{:ok, _notifs} = Notification.create_notifications(activity)
|
||||
Notification.clear(other_user)
|
||||
|
||||
|
|
|
@ -37,22 +37,24 @@ defp basic_auth_enc(username, password) do
|
|||
|
||||
describe "without an authorization header" do
|
||||
test "it halts the application" do
|
||||
conn = build_conn()
|
||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||
|> fetch_session
|
||||
|> AuthenticationPlug.call(%{})
|
||||
conn =
|
||||
build_conn()
|
||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||
|> fetch_session
|
||||
|> AuthenticationPlug.call(%{})
|
||||
|
||||
assert conn.status == 403
|
||||
assert conn.halted == true
|
||||
end
|
||||
|
||||
test "it assigns a nil user if the 'optional' option is used" do
|
||||
conn = build_conn()
|
||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||
|> fetch_session
|
||||
|> AuthenticationPlug.call(%{optional: true})
|
||||
conn =
|
||||
build_conn()
|
||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||
|> fetch_session
|
||||
|> AuthenticationPlug.call(%{optional: true})
|
||||
|
||||
assert %{ user: nil } == conn.assigns
|
||||
assert %{user: nil} == conn.assigns
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -73,9 +75,9 @@ test "it assigns a nil user if the 'optional' option is used" do
|
|||
build_conn()
|
||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||
|> fetch_session
|
||||
|> AuthenticationPlug.call(%{optional: true, fetcher: &fetch_nil/1 })
|
||||
|> AuthenticationPlug.call(%{optional: true, fetcher: &fetch_nil/1})
|
||||
|
||||
assert %{ user: nil } == conn.assigns
|
||||
assert %{user: nil} == conn.assigns
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -113,7 +115,7 @@ test "it assigns a nil user if the 'optional' option is used" do
|
|||
|> put_req_header("authorization", header)
|
||||
|> AuthenticationPlug.call(opts)
|
||||
|
||||
assert %{ user: nil } == conn.assigns
|
||||
assert %{user: nil} == conn.assigns
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -126,13 +128,14 @@ test "it assigns the user", %{conn: conn} do
|
|||
|
||||
header = basic_auth_enc("dude", "guy")
|
||||
|
||||
conn = conn
|
||||
conn =
|
||||
conn
|
||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||
|> fetch_session
|
||||
|> put_req_header("authorization", header)
|
||||
|> AuthenticationPlug.call(opts)
|
||||
|
||||
assert %{ user: @user } == conn.assigns
|
||||
assert %{user: @user} == conn.assigns
|
||||
assert get_session(conn, :user_id) == @user.id
|
||||
assert conn.halted == false
|
||||
end
|
||||
|
@ -147,7 +150,8 @@ test "it halts the appication", %{conn: conn} do
|
|||
|
||||
header = basic_auth_enc("dude", "guy")
|
||||
|
||||
conn = conn
|
||||
conn =
|
||||
conn
|
||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||
|> fetch_session
|
||||
|> put_req_header("authorization", header)
|
||||
|
@ -167,14 +171,15 @@ test "it assigns the user", %{conn: conn} do
|
|||
|
||||
header = basic_auth_enc("dude", "THIS IS WRONG")
|
||||
|
||||
conn = conn
|
||||
conn =
|
||||
conn
|
||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||
|> fetch_session
|
||||
|> put_session(:user_id, @user.id)
|
||||
|> put_req_header("authorization", header)
|
||||
|> AuthenticationPlug.call(opts)
|
||||
|
||||
assert %{ user: @user } == conn.assigns
|
||||
assert %{user: @user} == conn.assigns
|
||||
assert get_session(conn, :user_id) == @user.id
|
||||
assert conn.halted == false
|
||||
end
|
||||
|
@ -182,8 +187,9 @@ test "it assigns the user", %{conn: conn} do
|
|||
|
||||
describe "with an assigned user" do
|
||||
test "it does nothing, returning the incoming conn", %{conn: conn} do
|
||||
conn = conn
|
||||
|> assign(:user, @user)
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, @user)
|
||||
|
||||
conn_result = AuthenticationPlug.call(conn, %{})
|
||||
|
||||
|
|
|
@ -4,17 +4,19 @@ defmodule Pleroma.Builders.ActivityBuilder do
|
|||
|
||||
def build(data \\ %{}, opts \\ %{}) do
|
||||
user = opts[:user] || Pleroma.Factory.insert(:user)
|
||||
|
||||
activity = %{
|
||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_object_id,
|
||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
|
||||
"actor" => user.ap_id,
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"type" => "Create",
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "test",
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
}
|
||||
}
|
||||
|
||||
Map.merge(activity, data)
|
||||
end
|
||||
|
||||
|
@ -24,7 +26,7 @@ def insert(data \\ %{}, opts \\ %{}) do
|
|||
end
|
||||
|
||||
def insert_list(times, data \\ %{}, opts \\ %{}) do
|
||||
Enum.map(1..times, fn (n) ->
|
||||
Enum.map(1..times, fn n ->
|
||||
{:ok, activity} = insert(data, opts)
|
||||
activity
|
||||
end)
|
||||
|
|
|
@ -10,6 +10,7 @@ def build(data \\ %{}) do
|
|||
bio: "A tester.",
|
||||
ap_id: "some id"
|
||||
}
|
||||
|
||||
Map.merge(user, data)
|
||||
end
|
||||
|
||||
|
|
|
@ -25,13 +25,13 @@ defmodule Pleroma.Web.ChannelCase do
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
setup tags do
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
|
||||
|
||||
unless tags[:async] do
|
||||
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, {:shared, self()})
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -26,14 +26,14 @@ defmodule Pleroma.Web.ConnCase do
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
setup tags do
|
||||
Cachex.clear(:user_cache)
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
|
||||
|
||||
unless tags[:async] do
|
||||
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, {:shared, self()})
|
||||
end
|
||||
|
||||
{:ok, conn: Phoenix.ConnTest.build_conn()}
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -9,20 +9,27 @@ def user_factory do
|
|||
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
|
||||
bio: sequence(:bio, &"Tester Number #{&1}")
|
||||
}
|
||||
%{ user | ap_id: Pleroma.User.ap_id(user), follower_address: Pleroma.User.ap_followers(user), following: [Pleroma.User.ap_id(user)] }
|
||||
|
||||
%{
|
||||
user
|
||||
| ap_id: Pleroma.User.ap_id(user),
|
||||
follower_address: Pleroma.User.ap_followers(user),
|
||||
following: [Pleroma.User.ap_id(user)]
|
||||
}
|
||||
end
|
||||
|
||||
def note_factory do
|
||||
text = sequence(:text, &"This is :moominmamma: note #{&1}")
|
||||
|
||||
user = insert(:user)
|
||||
|
||||
data = %{
|
||||
"type" => "Note",
|
||||
"content" => text,
|
||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_object_id,
|
||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
|
||||
"actor" => user.ap_id,
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"published" => DateTime.utc_now() |> DateTime.to_iso8601,
|
||||
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||
"likes" => [],
|
||||
"like_count" => 0,
|
||||
"context" => "2hu",
|
||||
|
@ -40,13 +47,14 @@ def note_factory do
|
|||
|
||||
def note_activity_factory do
|
||||
note = insert(:note)
|
||||
|
||||
data = %{
|
||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id,
|
||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||
"type" => "Create",
|
||||
"actor" => note.data["actor"],
|
||||
"to" => note.data["to"],
|
||||
"object" => note.data,
|
||||
"published" => DateTime.utc_now() |> DateTime.to_iso8601,
|
||||
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||
"context" => note.data["context"]
|
||||
}
|
||||
|
||||
|
@ -62,11 +70,11 @@ def like_activity_factory do
|
|||
user = insert(:user)
|
||||
|
||||
data = %{
|
||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id,
|
||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||
"actor" => user.ap_id,
|
||||
"type" => "Like",
|
||||
"object" => note_activity.data["object"]["id"],
|
||||
"published_at" => DateTime.utc_now() |> DateTime.to_iso8601
|
||||
"published_at" => DateTime.utc_now() |> DateTime.to_iso8601()
|
||||
}
|
||||
|
||||
%Pleroma.Activity{
|
||||
|
@ -79,11 +87,11 @@ def follow_activity_factory do
|
|||
followed = insert(:user)
|
||||
|
||||
data = %{
|
||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id,
|
||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||
"actor" => follower.ap_id,
|
||||
"type" => "Follow",
|
||||
"object" => followed.ap_id,
|
||||
"published_at" => DateTime.utc_now() |> DateTime.to_iso8601
|
||||
"published_at" => DateTime.utc_now() |> DateTime.to_iso8601()
|
||||
}
|
||||
|
||||
%Pleroma.Activity{
|
||||
|
@ -96,7 +104,7 @@ def websub_subscription_factory do
|
|||
topic: "http://example.org",
|
||||
callback: "http://example/org/callback",
|
||||
secret: "here's a secret",
|
||||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now, 100),
|
||||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 100),
|
||||
state: "requested"
|
||||
}
|
||||
end
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,6 @@
|
|||
defmodule Pleroma.Web.OStatusMock do
|
||||
import Pleroma.Factory
|
||||
|
||||
def handle_incoming(_doc) do
|
||||
insert(:note_activity)
|
||||
end
|
||||
|
|
|
@ -2,4 +2,3 @@
|
|||
|
||||
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
|
||||
{:ok, _} = Application.ensure_all_started(:ex_machina)
|
||||
|
||||
|
|
|
@ -4,20 +4,37 @@ defmodule Pleroma.UploadTest do
|
|||
|
||||
describe "Storing a file" do
|
||||
test "copies the file to the configured folder" do
|
||||
file = %Plug.Upload{content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an [image.jpg"}
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an [image.jpg"
|
||||
}
|
||||
|
||||
data = Upload.store(file)
|
||||
assert data["name"] == "an [image.jpg"
|
||||
assert List.first(data["url"])["href"] == "http://localhost:4001/media/#{data["uuid"]}/an%20%5Bimage.jpg"
|
||||
|
||||
assert List.first(data["url"])["href"] ==
|
||||
"http://localhost:4001/media/#{data["uuid"]}/an%20%5Bimage.jpg"
|
||||
end
|
||||
|
||||
test "fixes an incorrect content type" do
|
||||
file = %Plug.Upload{content_type: "application/octet-stream", path: Path.absname("test/fixtures/image.jpg"), filename: "an [image.jpg"}
|
||||
file = %Plug.Upload{
|
||||
content_type: "application/octet-stream",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an [image.jpg"
|
||||
}
|
||||
|
||||
data = Upload.store(file)
|
||||
assert hd(data["url"])["mediaType"] == "image/jpeg"
|
||||
end
|
||||
|
||||
test "does not modify a valid content type" do
|
||||
file = %Plug.Upload{content_type: "image/png", path: Path.absname("test/fixtures/image.jpg"), filename: "an [image.jpg"}
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/png",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an [image.jpg"
|
||||
}
|
||||
|
||||
data = Upload.store(file)
|
||||
assert hd(data["url"])["mediaType"] == "image/png"
|
||||
end
|
||||
|
|
|
@ -10,15 +10,15 @@ defmodule Pleroma.UserTest do
|
|||
import Ecto.Query
|
||||
|
||||
test "ap_id returns the activity pub id for the user" do
|
||||
user = UserBuilder.build
|
||||
user = UserBuilder.build()
|
||||
|
||||
expected_ap_id = "#{Pleroma.Web.base_url}/users/#{user.nickname}"
|
||||
expected_ap_id = "#{Pleroma.Web.base_url()}/users/#{user.nickname}"
|
||||
|
||||
assert expected_ap_id == User.ap_id(user)
|
||||
end
|
||||
|
||||
test "ap_followers returns the followers collection for the user" do
|
||||
user = UserBuilder.build
|
||||
user = UserBuilder.build()
|
||||
|
||||
expected_followers_collection = "#{User.ap_id(user)}/followers"
|
||||
|
||||
|
@ -67,7 +67,7 @@ test "unfollow takes a user and another user" do
|
|||
followed = insert(:user)
|
||||
user = insert(:user, %{following: [User.ap_followers(followed)]})
|
||||
|
||||
{:ok, user, _activity } = User.unfollow(user, followed)
|
||||
{:ok, user, _activity} = User.unfollow(user, followed)
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
|
||||
|
@ -83,7 +83,6 @@ test "unfollow doesn't unfollow yourself" do
|
|||
assert user.following == [user.ap_id]
|
||||
end
|
||||
|
||||
|
||||
test "test if a user is following another user" do
|
||||
followed = insert(:user)
|
||||
user = insert(:user, %{following: [User.ap_followers(followed)]})
|
||||
|
@ -104,12 +103,12 @@ test "test if a user is following another user" do
|
|||
|
||||
test "it requires an email, name, nickname and password, bio is optional" do
|
||||
@full_user_data
|
||||
|> Map.keys
|
||||
|> Enum.each(fn (key) ->
|
||||
|> Map.keys()
|
||||
|> Enum.each(fn key ->
|
||||
params = Map.delete(@full_user_data, key)
|
||||
changeset = User.register_changeset(%User{}, params)
|
||||
|
||||
assert (if key == :bio, do: changeset.valid?, else: not changeset.valid?)
|
||||
assert if key == :bio, do: changeset.valid?, else: not changeset.valid?
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -120,7 +119,11 @@ test "it sets the password_hash, ap_id and following fields" do
|
|||
|
||||
assert is_binary(changeset.changes[:password_hash])
|
||||
assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname})
|
||||
assert changeset.changes[:following] == [User.ap_followers(%User{nickname: @full_user_data.nickname})]
|
||||
|
||||
assert changeset.changes[:following] == [
|
||||
User.ap_followers(%User{nickname: @full_user_data.nickname})
|
||||
]
|
||||
|
||||
assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
|
||||
end
|
||||
end
|
||||
|
@ -158,12 +161,24 @@ test "returns nil for nonexistant local user" do
|
|||
|
||||
test "returns an ap_id for a user" do
|
||||
user = insert(:user)
|
||||
assert User.ap_id(user) == Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :feed_redirect, user.nickname)
|
||||
|
||||
assert User.ap_id(user) ==
|
||||
Pleroma.Web.Router.Helpers.o_status_url(
|
||||
Pleroma.Web.Endpoint,
|
||||
:feed_redirect,
|
||||
user.nickname
|
||||
)
|
||||
end
|
||||
|
||||
test "returns an ap_followers link for a user" do
|
||||
user = insert(:user)
|
||||
assert User.ap_followers(user) == Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :feed_redirect, user.nickname) <> "/followers"
|
||||
|
||||
assert User.ap_followers(user) ==
|
||||
Pleroma.Web.Router.Helpers.o_status_url(
|
||||
Pleroma.Web.Endpoint,
|
||||
:feed_redirect,
|
||||
user.nickname
|
||||
) <> "/followers"
|
||||
end
|
||||
|
||||
describe "remote user creation changeset" do
|
||||
|
@ -184,7 +199,8 @@ test "it confirms validity" do
|
|||
test "it sets the follower_adress" do
|
||||
cs = User.remote_user_creation(@valid_remote)
|
||||
# remote users get a fake local follower address
|
||||
assert cs.changes.follower_address == User.ap_followers(%User{ nickname: @valid_remote[:nickname] })
|
||||
assert cs.changes.follower_address ==
|
||||
User.ap_followers(%User{nickname: @valid_remote[:nickname]})
|
||||
end
|
||||
|
||||
test "it enforces the fqn format for nicknames" do
|
||||
|
@ -196,7 +212,7 @@ test "it enforces the fqn format for nicknames" do
|
|||
|
||||
test "it has required fields" do
|
||||
[:name, :nickname, :ap_id]
|
||||
|> Enum.each(fn (field) ->
|
||||
|> Enum.each(fn field ->
|
||||
cs = User.remote_user_creation(Map.delete(@valid_remote, field))
|
||||
refute cs.valid?
|
||||
end)
|
||||
|
@ -204,7 +220,7 @@ test "it has required fields" do
|
|||
|
||||
test "it restricts some sizes" do
|
||||
[bio: 5000, name: 100]
|
||||
|> Enum.each(fn ({field, size}) ->
|
||||
|> Enum.each(fn {field, size} ->
|
||||
string = String.pad_leading(".", size)
|
||||
cs = User.remote_user_creation(Map.put(@valid_remote, field, string))
|
||||
assert cs.valid?
|
||||
|
@ -323,7 +339,11 @@ test "get recipients from activity" do
|
|||
user_two = insert(:user, local: false)
|
||||
addressed = insert(:user, local: true)
|
||||
addressed_remote = insert(:user, local: false)
|
||||
{:ok, activity} = CommonAPI.post(actor, %{"status" => "hey @#{addressed.nickname} @#{addressed_remote.nickname}"})
|
||||
|
||||
{:ok, activity} =
|
||||
CommonAPI.post(actor, %{
|
||||
"status" => "hey @#{addressed.nickname} @#{addressed_remote.nickname}"
|
||||
})
|
||||
|
||||
assert [addressed] == User.get_recipients_from_activity(activity)
|
||||
|
||||
|
@ -379,7 +399,7 @@ test "get_public_key_for_ap_id fetches a user that's not in the db" do
|
|||
|
||||
test "insert or update a user from given data" do
|
||||
user = insert(:user, %{nickname: "nick@name.de"})
|
||||
data = %{ ap_id: user.ap_id <> "xxx", name: user.name, nickname: user.nickname }
|
||||
data = %{ap_id: user.ap_id <> "xxx", name: user.name, nickname: user.nickname}
|
||||
|
||||
assert {:ok, %User{}} = User.insert_or_update_user(data)
|
||||
end
|
||||
|
|
|
@ -9,9 +9,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
test "it returns a json representation of the user", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
conn = conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get("/users/#{user.nickname}")
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get("/users/#{user.nickname}")
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
|
||||
|
@ -22,11 +23,12 @@ test "it returns a json representation of the user", %{conn: conn} do
|
|||
describe "/object/:uuid" do
|
||||
test "it returns a json representation of the object", %{conn: conn} do
|
||||
note = insert(:note)
|
||||
uuid = String.split(note.data["id"], "/") |> List.last
|
||||
uuid = String.split(note.data["id"], "/") |> List.last()
|
||||
|
||||
conn = conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get("/objects/#{uuid}")
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get("/objects/#{uuid}")
|
||||
|
||||
assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
|
||||
end
|
||||
|
@ -34,12 +36,13 @@ test "it returns a json representation of the object", %{conn: conn} do
|
|||
|
||||
describe "/users/:nickname/inbox" do
|
||||
test "it inserts an incoming activity into the database", %{conn: conn} do
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
|
||||
|
||||
conn = conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/inbox", data)
|
||||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/inbox", data)
|
||||
|
||||
assert "ok" == json_response(conn, 200)
|
||||
:timer.sleep(500)
|
||||
|
|
|
@ -22,7 +22,7 @@ test "it returns a user" do
|
|||
describe "insertion" do
|
||||
test "returns the activity if one with the same id is already in" do
|
||||
activity = insert(:note_activity)
|
||||
{:ok, new_activity}= ActivityPub.insert(activity.data)
|
||||
{:ok, new_activity} = ActivityPub.insert(activity.data)
|
||||
|
||||
assert activity == new_activity
|
||||
end
|
||||
|
@ -37,6 +37,7 @@ test "inserts a given map into the activity database, giving it an id if it has
|
|||
assert is_binary(activity.data["id"])
|
||||
|
||||
given_id = "bla"
|
||||
|
||||
data = %{
|
||||
"ok" => true,
|
||||
"id" => given_id
|
||||
|
@ -63,7 +64,14 @@ test "adds an id to a given object if it lacks one and is a note and inserts it
|
|||
|
||||
describe "create activities" do
|
||||
test "removes doubled 'to' recipients" do
|
||||
{:ok, activity} = ActivityPub.create(%{to: ["user1", "user1", "user2"], actor: %User{ap_id: "1"}, context: "", object: %{}})
|
||||
{:ok, activity} =
|
||||
ActivityPub.create(%{
|
||||
to: ["user1", "user1", "user2"],
|
||||
actor: %User{ap_id: "1"},
|
||||
context: "",
|
||||
object: %{}
|
||||
})
|
||||
|
||||
assert activity.data["to"] == ["user1", "user2"]
|
||||
assert activity.actor == "1"
|
||||
assert activity.recipients == ["user1", "user2"]
|
||||
|
@ -124,11 +132,11 @@ test "doesn't return blocked activities" do
|
|||
|
||||
describe "public fetch activities" do
|
||||
test "retrieves public activities" do
|
||||
_activities = ActivityPub.fetch_public_activities
|
||||
_activities = ActivityPub.fetch_public_activities()
|
||||
|
||||
%{public: public} = ActivityBuilder.public_and_non_public
|
||||
%{public: public} = ActivityBuilder.public_and_non_public()
|
||||
|
||||
activities = ActivityPub.fetch_public_activities
|
||||
activities = ActivityPub.fetch_public_activities()
|
||||
assert length(activities) == 1
|
||||
assert Enum.at(activities, 0) == public
|
||||
end
|
||||
|
@ -137,7 +145,7 @@ test "retrieves a maximum of 20 activities" do
|
|||
activities = ActivityBuilder.insert_list(30)
|
||||
last_expected = List.last(activities)
|
||||
|
||||
activities = ActivityPub.fetch_public_activities
|
||||
activities = ActivityPub.fetch_public_activities()
|
||||
last = List.last(activities)
|
||||
|
||||
assert length(activities) == 20
|
||||
|
@ -232,7 +240,12 @@ test "adds an announce activity to the db" do
|
|||
{:ok, announce_activity, object} = ActivityPub.announce(user, object)
|
||||
assert object.data["announcement_count"] == 1
|
||||
assert object.data["announcements"] == [user.ap_id]
|
||||
assert announce_activity.data["to"] == [User.ap_followers(user), note_activity.data["actor"]]
|
||||
|
||||
assert announce_activity.data["to"] == [
|
||||
User.ap_followers(user),
|
||||
note_activity.data["actor"]
|
||||
]
|
||||
|
||||
assert announce_activity.data["object"] == object.data["id"]
|
||||
assert announce_activity.data["actor"] == user.ap_id
|
||||
assert announce_activity.data["context"] == object.data["context"]
|
||||
|
@ -241,7 +254,11 @@ test "adds an announce activity to the db" do
|
|||
|
||||
describe "uploading files" do
|
||||
test "copies the file to the configured folder" do
|
||||
file = %Plug.Upload{content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg"}
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an_image.jpg"
|
||||
}
|
||||
|
||||
{:ok, %Object{} = object} = ActivityPub.upload(file)
|
||||
assert object.data["name"] == "an_image.jpg"
|
||||
|
@ -268,11 +285,14 @@ test "fetches the latest Follow activity" do
|
|||
|
||||
describe "fetching an object" do
|
||||
test "it fetches an object" do
|
||||
{:ok, object} = ActivityPub.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
|
||||
{:ok, object} =
|
||||
ActivityPub.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
|
||||
|
||||
assert activity = Activity.get_create_activity_by_object_ap_id(object.data["id"])
|
||||
assert activity.data["id"]
|
||||
|
||||
{:ok, object_again} = ActivityPub.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
|
||||
{:ok, object_again} =
|
||||
ActivityPub.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
|
||||
|
||||
assert [attachment] = object.data["attachment"]
|
||||
assert is_list(attachment["url"])
|
||||
|
@ -285,7 +305,8 @@ test "it works with objects only available via Ostatus" do
|
|||
assert activity = Activity.get_create_activity_by_object_ap_id(object.data["id"])
|
||||
assert activity.data["id"]
|
||||
|
||||
{:ok, object_again} = ActivityPub.fetch_object_from_id("https://shitposter.club/notice/2827873")
|
||||
{:ok, object_again} =
|
||||
ActivityPub.fetch_object_from_id("https://shitposter.club/notice/2827873")
|
||||
|
||||
assert object == object_again
|
||||
end
|
||||
|
@ -344,7 +365,14 @@ test "it creates an update activity with the new user data" do
|
|||
user = insert(:user)
|
||||
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
|
||||
user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
|
||||
{:ok, update} = ActivityPub.update(%{actor: user_data["id"], to: [user.follower_address], cc: [], object: user_data})
|
||||
|
||||
{:ok, update} =
|
||||
ActivityPub.update(%{
|
||||
actor: user_data["id"],
|
||||
to: [user.follower_address],
|
||||
cc: [],
|
||||
object: user_data
|
||||
})
|
||||
|
||||
assert update.data["actor"] == user.ap_id
|
||||
assert update.data["to"] == [user.follower_address]
|
||||
|
|
|
@ -16,9 +16,10 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
|||
test "it ignores an incoming notice if we already have it" do
|
||||
activity = insert(:note_activity)
|
||||
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json")
|
||||
|> Poison.decode!
|
||||
|> Map.put("object", activity.data["object"])
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||
|> Poison.decode!()
|
||||
|> Map.put("object", activity.data["object"])
|
||||
|
||||
{:ok, returned_activity} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
|
@ -26,51 +27,72 @@ test "it ignores an incoming notice if we already have it" do
|
|||
end
|
||||
|
||||
test "it fetches replied-to activities if we don't have them" do
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json")
|
||||
|> Poison.decode!
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||
|> Poison.decode!()
|
||||
|
||||
object = data["object"]
|
||||
|> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
|
||||
object =
|
||||
data["object"]
|
||||
|> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
|
||||
|
||||
data = data
|
||||
|> Map.put("object", object)
|
||||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|
||||
{:ok, returned_activity} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert activity = Activity.get_create_activity_by_object_ap_id("tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment")
|
||||
assert returned_activity.data["object"]["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
|
||||
assert activity =
|
||||
Activity.get_create_activity_by_object_ap_id(
|
||||
"tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
|
||||
)
|
||||
|
||||
assert returned_activity.data["object"]["inReplyToAtomUri"] ==
|
||||
"https://shitposter.club/notice/2827873"
|
||||
|
||||
assert returned_activity.data["object"]["inReplyToStatusId"] == activity.id
|
||||
end
|
||||
|
||||
test "it works for incoming notices" do
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
assert data["id"] == "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity"
|
||||
assert data["context"] == "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
|
||||
|
||||
assert data["id"] ==
|
||||
"http://mastodon.example.org/users/admin/statuses/99512778738411822/activity"
|
||||
|
||||
assert data["context"] ==
|
||||
"tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
|
||||
|
||||
assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
|
||||
assert data["cc"] == [
|
||||
"http://mastodon.example.org/users/admin/followers",
|
||||
"http://localtesting.pleroma.lol/users/lain"
|
||||
]
|
||||
"http://mastodon.example.org/users/admin/followers",
|
||||
"http://localtesting.pleroma.lol/users/lain"
|
||||
]
|
||||
|
||||
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||
|
||||
object = data["object"]
|
||||
assert object["id"] == "http://mastodon.example.org/users/admin/statuses/99512778738411822"
|
||||
|
||||
assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
|
||||
assert object["cc"] == [
|
||||
"http://mastodon.example.org/users/admin/followers",
|
||||
"http://localtesting.pleroma.lol/users/lain"
|
||||
]
|
||||
"http://mastodon.example.org/users/admin/followers",
|
||||
"http://localtesting.pleroma.lol/users/lain"
|
||||
]
|
||||
|
||||
assert object["actor"] == "http://mastodon.example.org/users/admin"
|
||||
assert object["attributedTo"] == "http://mastodon.example.org/users/admin"
|
||||
assert object["context"] == "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
|
||||
|
||||
assert object["context"] ==
|
||||
"tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
|
||||
|
||||
assert object["sensitive"] == true
|
||||
end
|
||||
|
||||
test "it works for incoming notices with hashtags" do
|
||||
data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Poison.decode!
|
||||
data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Poison.decode!()
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
assert Enum.at(data["object"]["tag"], 2) == "moo"
|
||||
|
@ -78,8 +100,10 @@ test "it works for incoming notices with hashtags" do
|
|||
|
||||
test "it works for incoming follow requests" do
|
||||
user = insert(:user)
|
||||
data = File.read!("test/fixtures/mastodon-follow-activity.json") |> Poison.decode!
|
||||
|> Map.put("object", user.ap_id)
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-follow-activity.json") |> Poison.decode!()
|
||||
|> Map.put("object", user.ap_id)
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
|
@ -93,8 +117,9 @@ test "it works for incoming likes" do
|
|||
user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
||||
|
||||
data = File.read!("test/fixtures/mastodon-like.json") |> Poison.decode!
|
||||
|> Map.put("object", activity.data["object"]["id"])
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-like.json") |> Poison.decode!()
|
||||
|> Map.put("object", activity.data["object"]["id"])
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
|
@ -105,14 +130,18 @@ test "it works for incoming likes" do
|
|||
end
|
||||
|
||||
test "it works for incoming announces" do
|
||||
data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!
|
||||
data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||
assert data["type"] == "Announce"
|
||||
assert data["id"] == "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
|
||||
assert data["object"] == "http://mastodon.example.org/users/admin/statuses/99541947525187367"
|
||||
|
||||
assert data["id"] ==
|
||||
"http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
|
||||
|
||||
assert data["object"] ==
|
||||
"http://mastodon.example.org/users/admin/statuses/99541947525187367"
|
||||
|
||||
assert Activity.get_create_activity_by_object_ap_id(data["object"])
|
||||
end
|
||||
|
@ -121,53 +150,77 @@ test "it works for incoming announces with an existing activity" do
|
|||
user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
|
||||
|
||||
data = File.read!("test/fixtures/mastodon-announce.json")
|
||||
|> Poison.decode!
|
||||
|> Map.put("object", activity.data["object"]["id"])
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-announce.json")
|
||||
|> Poison.decode!()
|
||||
|> Map.put("object", activity.data["object"]["id"])
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||
assert data["type"] == "Announce"
|
||||
assert data["id"] == "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
|
||||
|
||||
assert data["id"] ==
|
||||
"http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
|
||||
|
||||
assert data["object"] == activity.data["object"]["id"]
|
||||
|
||||
assert Activity.get_create_activity_by_object_ap_id(data["object"]).id == activity.id
|
||||
end
|
||||
|
||||
test "it works for incoming update activities" do
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!
|
||||
object = update_data["object"]
|
||||
|> Map.put("actor", data["actor"])
|
||||
|> Map.put("id", data["actor"])
|
||||
update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
|
||||
|
||||
update_data = update_data
|
||||
|> Map.put("actor", data["actor"])
|
||||
|> Map.put("object", object)
|
||||
object =
|
||||
update_data["object"]
|
||||
|> Map.put("actor", data["actor"])
|
||||
|> Map.put("id", data["actor"])
|
||||
|
||||
update_data =
|
||||
update_data
|
||||
|> Map.put("actor", data["actor"])
|
||||
|> Map.put("object", object)
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
|
||||
|
||||
user = User.get_cached_by_ap_id(data["actor"])
|
||||
assert user.name == "gargle"
|
||||
assert user.avatar["url"] == [%{"href" => "https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"}]
|
||||
assert user.info["banner"]["url"] == [%{"href" => "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"}]
|
||||
|
||||
assert user.avatar["url"] == [
|
||||
%{
|
||||
"href" =>
|
||||
"https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
|
||||
}
|
||||
]
|
||||
|
||||
assert user.info["banner"]["url"] == [
|
||||
%{
|
||||
"href" =>
|
||||
"https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
|
||||
}
|
||||
]
|
||||
|
||||
assert user.bio == "<p>Some bio</p>"
|
||||
end
|
||||
|
||||
test "it works for incoming deletes" do
|
||||
activity = insert(:note_activity)
|
||||
data = File.read!("test/fixtures/mastodon-delete.json")
|
||||
|> Poison.decode!
|
||||
|
||||
object = data["object"]
|
||||
|> Map.put("id", activity.data["object"]["id"])
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-delete.json")
|
||||
|> Poison.decode!()
|
||||
|
||||
data = data
|
||||
|> Map.put("object", object)
|
||||
|> Map.put("actor", activity.data["actor"])
|
||||
object =
|
||||
data["object"]
|
||||
|> Map.put("id", activity.data["object"]["id"])
|
||||
|
||||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|> Map.put("actor", activity.data["actor"])
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
|
@ -180,7 +233,8 @@ test "it turns mentions into tags" do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"})
|
||||
{:ok, activity} =
|
||||
CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"})
|
||||
|
||||
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
object = modified["object"]
|
||||
|
@ -192,7 +246,7 @@ test "it turns mentions into tags" do
|
|||
}
|
||||
|
||||
expected_tag = %{
|
||||
"href" => Pleroma.Web.Endpoint.url <> "/tags/2hu",
|
||||
"href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
|
||||
"type" => "Hashtag",
|
||||
"name" => "#2hu"
|
||||
}
|
||||
|
@ -247,7 +301,9 @@ test "it translates ostatus reply_to IDs to external URLs" do
|
|||
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "HI!", "in_reply_to_status_id" => referred_activity.id})
|
||||
{:ok, activity} =
|
||||
CommonAPI.post(user, %{"status" => "HI!", "in_reply_to_status_id" => referred_activity.id})
|
||||
|
||||
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
|
||||
assert modified["object"]["inReplyTo"] == "http://gs.example.org:4040/index.php/notice/29"
|
||||
|
@ -256,7 +312,14 @@ test "it translates ostatus reply_to IDs to external URLs" do
|
|||
|
||||
describe "user upgrade" do
|
||||
test "it upgrades a user to activitypub" do
|
||||
user = insert(:user, %{nickname: "rye@niu.moe", local: false, ap_id: "https://niu.moe/users/rye", follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})})
|
||||
user =
|
||||
insert(:user, %{
|
||||
nickname: "rye@niu.moe",
|
||||
local: false,
|
||||
ap_id: "https://niu.moe/users/rye",
|
||||
follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
|
||||
})
|
||||
|
||||
user_two = insert(:user, %{following: [user.follower_address]})
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
|
||||
|
@ -279,8 +342,25 @@ test "it upgrades a user to activitypub" do
|
|||
|
||||
activity = Repo.get(Activity, activity.id)
|
||||
assert user.follower_address in activity.recipients
|
||||
assert %{"url" => [%{"href" => "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"}]} = user.avatar
|
||||
assert %{"url" => [%{"href" => "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"}]} = user.info["banner"]
|
||||
|
||||
assert %{
|
||||
"url" => [
|
||||
%{
|
||||
"href" =>
|
||||
"https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
|
||||
}
|
||||
]
|
||||
} = user.avatar
|
||||
|
||||
assert %{
|
||||
"url" => [
|
||||
%{
|
||||
"href" =>
|
||||
"https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
|
||||
}
|
||||
]
|
||||
} = user.info["banner"]
|
||||
|
||||
refute "..." in activity.recipients
|
||||
|
||||
unrelated_activity = Repo.get(Activity, unrelated_activity.id)
|
||||
|
|
|
@ -3,7 +3,8 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
|
|||
use Pleroma.DataCase
|
||||
|
||||
test "it adds attachment links to a given text and attachment set" do
|
||||
name = "Sakura%20Mana%20%E2%80%93%20Turned%20on%20by%20a%20Senior%20OL%20with%20a%20Temptating%20Tight%20Skirt-s%20Full%20Hipline%20and%20Panty%20Shot-%20Beautiful%20Thick%20Thighs-%20and%20Erotic%20Ass-%20-2015-%20--%20Oppaitime%208-28-2017%206-50-33%20PM.png"
|
||||
name =
|
||||
"Sakura%20Mana%20%E2%80%93%20Turned%20on%20by%20a%20Senior%20OL%20with%20a%20Temptating%20Tight%20Skirt-s%20Full%20Hipline%20and%20Panty%20Shot-%20Beautiful%20Thick%20Thighs-%20and%20Erotic%20Ass-%20-2015-%20--%20Oppaitime%208-28-2017%206-50-33%20PM.png"
|
||||
|
||||
attachment = %{
|
||||
"url" => [%{"href" => name}]
|
||||
|
@ -11,6 +12,7 @@ test "it adds attachment links to a given text and attachment set" do
|
|||
|
||||
res = Utils.add_attachments("", [attachment])
|
||||
|
||||
assert res == "<br><a href=\"#{name}\" class='attachment'>Sakura Mana – Turned on by a Se…</a>"
|
||||
assert res ==
|
||||
"<br><a href=\"#{name}\" class='attachment'>Sakura Mana – Turned on by a Se…</a>"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
|||
alias Pleroma.User
|
||||
|
||||
test "Represent a user account" do
|
||||
user = insert(:user, %{info: %{"note_count" => 5, "follower_count" => 3}, nickname: "shp@shitposter.club", inserted_at: ~N[2017-08-15 15:47:06.597036]})
|
||||
user =
|
||||
insert(:user, %{
|
||||
info: %{"note_count" => 5, "follower_count" => 3},
|
||||
nickname: "shp@shitposter.club",
|
||||
inserted_at: ~N[2017-08-15 15:47:06.597036]
|
||||
})
|
||||
|
||||
expected = %{
|
||||
id: to_string(user.id),
|
||||
|
|
|
@ -14,17 +14,19 @@ test "the home timeline", %{conn: conn} do
|
|||
|
||||
{:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/timelines/home")
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/timelines/home")
|
||||
|
||||
assert length(json_response(conn, 200)) == 0
|
||||
|
||||
{:ok, user} = User.follow(user, following)
|
||||
|
||||
conn = build_conn()
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/timelines/home")
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/timelines/home")
|
||||
|
||||
assert [%{"content" => "test"}] = json_response(conn, 200)
|
||||
end
|
||||
|
@ -32,44 +34,57 @@ test "the home timeline", %{conn: conn} do
|
|||
test "the public timeline", %{conn: conn} do
|
||||
following = insert(:user)
|
||||
|
||||
capture_log fn ->
|
||||
capture_log(fn ->
|
||||
{:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
|
||||
{:ok, [_activity]} = OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
|
||||
|
||||
conn = conn
|
||||
|> get("/api/v1/timelines/public", %{"local" => "False"})
|
||||
{:ok, [_activity]} =
|
||||
OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/timelines/public", %{"local" => "False"})
|
||||
|
||||
assert length(json_response(conn, 200)) == 2
|
||||
|
||||
conn = build_conn()
|
||||
|> get("/api/v1/timelines/public", %{"local" => "True"})
|
||||
conn =
|
||||
build_conn()
|
||||
|> get("/api/v1/timelines/public", %{"local" => "True"})
|
||||
|
||||
assert [%{"content" => "test"}] = json_response(conn, 200)
|
||||
|
||||
conn = build_conn()
|
||||
|> get("/api/v1/timelines/public", %{"local" => "1"})
|
||||
conn =
|
||||
build_conn()
|
||||
|> get("/api/v1/timelines/public", %{"local" => "1"})
|
||||
|
||||
assert [%{"content" => "test"}] = json_response(conn, 200)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
test "posting a status", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses", %{"status" => "cofe", "spoiler_text" => "2hu", "sensitive" => "false"})
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses", %{
|
||||
"status" => "cofe",
|
||||
"spoiler_text" => "2hu",
|
||||
"sensitive" => "false"
|
||||
})
|
||||
|
||||
assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
|
||||
json_response(conn, 200)
|
||||
|
||||
assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} = json_response(conn, 200)
|
||||
assert Repo.get(Activity, id)
|
||||
end
|
||||
|
||||
test "posting a sensitive status", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
|
||||
|
||||
assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
|
||||
assert Repo.get(Activity, id)
|
||||
|
@ -80,9 +95,10 @@ test "replying to a status", %{conn: conn} do
|
|||
|
||||
{:ok, replied_to} = TwitterAPI.create_status(user, %{"status" => "cofe"})
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
|
||||
|
||||
assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
|
||||
|
||||
|
@ -95,9 +111,10 @@ test "replying to a status", %{conn: conn} do
|
|||
test "verify_credentials", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/accounts/verify_credentials")
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/accounts/verify_credentials")
|
||||
|
||||
assert %{"id" => id} = json_response(conn, 200)
|
||||
assert id == to_string(user.id)
|
||||
|
@ -106,8 +123,9 @@ test "verify_credentials", %{conn: conn} do
|
|||
test "get a status", %{conn: conn} do
|
||||
activity = insert(:note_activity)
|
||||
|
||||
conn = conn
|
||||
|> get("/api/v1/statuses/#{activity.id}")
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/statuses/#{activity.id}")
|
||||
|
||||
assert %{"id" => id} = json_response(conn, 200)
|
||||
assert id == to_string(activity.id)
|
||||
|
@ -118,9 +136,10 @@ test "when you created it", %{conn: conn} do
|
|||
activity = insert(:note_activity)
|
||||
author = User.get_by_ap_id(activity.data["actor"])
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, author)
|
||||
|> delete("/api/v1/statuses/#{activity.id}")
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, author)
|
||||
|> delete("/api/v1/statuses/#{activity.id}")
|
||||
|
||||
assert %{} = json_response(conn, 200)
|
||||
|
||||
|
@ -131,9 +150,10 @@ test "when you didn't create it", %{conn: conn} do
|
|||
activity = insert(:note_activity)
|
||||
user = insert(:user)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> delete("/api/v1/statuses/#{activity.id}")
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> delete("/api/v1/statuses/#{activity.id}")
|
||||
|
||||
assert %{"error" => _} = json_response(conn, 403)
|
||||
|
||||
|
@ -146,14 +166,19 @@ test "list of notifications", %{conn: conn} do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
|
||||
{:ok, activity} =
|
||||
TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
|
||||
|
||||
{:ok, [_notification]} = Notification.create_notifications(activity)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/notifications")
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/notifications")
|
||||
|
||||
expected_response =
|
||||
"hi <span><a href=\"#{user.ap_id}\">@<span>#{user.nickname}</span></a></span>"
|
||||
|
||||
expected_response = "hi <span><a href=\"#{user.ap_id}\">@<span>#{user.nickname}</span></a></span>"
|
||||
assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
|
||||
assert response == expected_response
|
||||
end
|
||||
|
@ -162,14 +187,19 @@ test "getting a single notification", %{conn: conn} do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
|
||||
{:ok, activity} =
|
||||
TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
|
||||
|
||||
{:ok, [notification]} = Notification.create_notifications(activity)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/notifications/#{notification.id}")
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/notifications/#{notification.id}")
|
||||
|
||||
expected_response =
|
||||
"hi <span><a href=\"#{user.ap_id}\">@<span>#{user.nickname}</span></a></span>"
|
||||
|
||||
expected_response = "hi <span><a href=\"#{user.ap_id}\">@<span>#{user.nickname}</span></a></span>"
|
||||
assert %{"status" => %{"content" => response}} = json_response(conn, 200)
|
||||
assert response == expected_response
|
||||
end
|
||||
|
@ -178,12 +208,15 @@ test "dismissing a single notification", %{conn: conn} do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
|
||||
{:ok, activity} =
|
||||
TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
|
||||
|
||||
{:ok, [notification]} = Notification.create_notifications(activity)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
|
||||
|
||||
assert %{} = json_response(conn, 200)
|
||||
end
|
||||
|
@ -192,18 +225,22 @@ test "clearing all notifications", %{conn: conn} do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
|
||||
{:ok, activity} =
|
||||
TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
|
||||
|
||||
{:ok, [_notification]} = Notification.create_notifications(activity)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/notifications/clear")
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/notifications/clear")
|
||||
|
||||
assert %{} = json_response(conn, 200)
|
||||
|
||||
conn = build_conn()
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/notifications")
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/notifications")
|
||||
|
||||
assert all = json_response(conn, 200)
|
||||
assert all == []
|
||||
|
@ -215,11 +252,14 @@ test "reblogs and returns the reblogged status", %{conn: conn} do
|
|||
activity = insert(:note_activity)
|
||||
user = insert(:user)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses/#{activity.id}/reblog")
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses/#{activity.id}/reblog")
|
||||
|
||||
assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
|
||||
json_response(conn, 200)
|
||||
|
||||
assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} = json_response(conn, 200)
|
||||
assert to_string(activity.id) == id
|
||||
end
|
||||
end
|
||||
|
@ -229,11 +269,14 @@ test "favs a status and returns it", %{conn: conn} do
|
|||
activity = insert(:note_activity)
|
||||
user = insert(:user)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses/#{activity.id}/favourite")
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses/#{activity.id}/favourite")
|
||||
|
||||
assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
|
||||
json_response(conn, 200)
|
||||
|
||||
assert %{"id" => id, "favourites_count" => 1, "favourited" => true} = json_response(conn, 200)
|
||||
assert to_string(activity.id) == id
|
||||
end
|
||||
end
|
||||
|
@ -245,11 +288,14 @@ test "unfavorites a status and returns it", %{conn: conn} do
|
|||
|
||||
{:ok, _, _} = CommonAPI.favorite(activity.id, user)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses/#{activity.id}/unfavourite")
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses/#{activity.id}/unfavourite")
|
||||
|
||||
assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
|
||||
json_response(conn, 200)
|
||||
|
||||
assert %{"id" => id, "favourites_count" => 0, "favourited" => false} = json_response(conn, 200)
|
||||
assert to_string(activity.id) == id
|
||||
end
|
||||
end
|
||||
|
@ -261,8 +307,9 @@ test "gets a users statuses", %{conn: conn} do
|
|||
|
||||
user = User.get_by_ap_id(note_two.data["actor"])
|
||||
|
||||
conn = conn
|
||||
|> get("/api/v1/accounts/#{user.id}/statuses")
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/accounts/#{user.id}/statuses")
|
||||
|
||||
assert [%{"id" => id}] = json_response(conn, 200)
|
||||
|
||||
|
@ -273,20 +320,29 @@ test "gets an users media", %{conn: conn} do
|
|||
note = insert(:note_activity)
|
||||
user = User.get_by_ap_id(note.data["actor"])
|
||||
|
||||
file = %Plug.Upload{content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg"}
|
||||
media = TwitterAPI.upload(file, "json")
|
||||
|> Poison.decode!
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an_image.jpg"
|
||||
}
|
||||
|
||||
{:ok, image_post} = TwitterAPI.create_status(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]})
|
||||
media =
|
||||
TwitterAPI.upload(file, "json")
|
||||
|> Poison.decode!()
|
||||
|
||||
conn = conn
|
||||
|> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
|
||||
{:ok, image_post} =
|
||||
TwitterAPI.create_status(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]})
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
|
||||
|
||||
assert [%{"id" => id}] = json_response(conn, 200)
|
||||
assert id == to_string(image_post.id)
|
||||
|
||||
conn = build_conn()
|
||||
|> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
|
||||
conn =
|
||||
build_conn()
|
||||
|> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
|
||||
|
||||
assert [%{"id" => id}] = json_response(conn, 200)
|
||||
assert id == to_string(image_post.id)
|
||||
|
@ -299,9 +355,10 @@ test "returns the relationships for the current user", %{conn: conn} do
|
|||
other_user = insert(:user)
|
||||
{:ok, user} = User.follow(user, other_user)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
|
||||
|
||||
assert [relationship] = json_response(conn, 200)
|
||||
|
||||
|
@ -312,26 +369,33 @@ test "returns the relationships for the current user", %{conn: conn} do
|
|||
test "account fetching", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
conn = conn
|
||||
|> get("/api/v1/accounts/#{user.id}")
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/accounts/#{user.id}")
|
||||
|
||||
assert %{"id" => id} = json_response(conn, 200)
|
||||
assert id == to_string(user.id)
|
||||
|
||||
conn = build_conn()
|
||||
|> get("/api/v1/accounts/-1")
|
||||
conn =
|
||||
build_conn()
|
||||
|> get("/api/v1/accounts/-1")
|
||||
|
||||
assert %{"error" => "Can't find user"} = json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "media upload", %{conn: conn} do
|
||||
file = %Plug.Upload{content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg"}
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an_image.jpg"
|
||||
}
|
||||
|
||||
user = insert(:user)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/media", %{"file" => file})
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/media", %{"file" => file})
|
||||
|
||||
assert media = json_response(conn, 200)
|
||||
|
||||
|
@ -341,16 +405,20 @@ test "media upload", %{conn: conn} do
|
|||
test "hashtag timeline", %{conn: conn} do
|
||||
following = insert(:user)
|
||||
|
||||
capture_log fn ->
|
||||
capture_log(fn ->
|
||||
{:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"})
|
||||
{:ok, [_activity]} = OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
|
||||
conn = conn
|
||||
|> get("/api/v1/timelines/tag/2hu")
|
||||
|
||||
{:ok, [_activity]} =
|
||||
OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/timelines/tag/2hu")
|
||||
|
||||
assert [%{"id" => id}] = json_response(conn, 200)
|
||||
|
||||
assert id == to_string(activity.id)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
test "getting followers", %{conn: conn} do
|
||||
|
@ -358,8 +426,9 @@ test "getting followers", %{conn: conn} do
|
|||
other_user = insert(:user)
|
||||
{:ok, user} = User.follow(user, other_user)
|
||||
|
||||
conn = conn
|
||||
|> get("/api/v1/accounts/#{other_user.id}/followers")
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/accounts/#{other_user.id}/followers")
|
||||
|
||||
assert [%{"id" => id}] = json_response(conn, 200)
|
||||
assert id == to_string(user.id)
|
||||
|
@ -370,8 +439,9 @@ test "getting following", %{conn: conn} do
|
|||
other_user = insert(:user)
|
||||
{:ok, user} = User.follow(user, other_user)
|
||||
|
||||
conn = conn
|
||||
|> get("/api/v1/accounts/#{user.id}/following")
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/accounts/#{user.id}/following")
|
||||
|
||||
assert [%{"id" => id}] = json_response(conn, 200)
|
||||
assert id == to_string(other_user.id)
|
||||
|
@ -381,23 +451,28 @@ test "following / unfollowing a user", %{conn: conn} do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/accounts/#{other_user.id}/follow")
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/accounts/#{other_user.id}/follow")
|
||||
|
||||
assert %{"id" => _id, "following" => true} = json_response(conn, 200)
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
conn = build_conn()
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/accounts/#{other_user.id}/unfollow")
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/accounts/#{other_user.id}/unfollow")
|
||||
|
||||
assert %{"id" => _id, "following" => false} = json_response(conn, 200)
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
conn = build_conn()
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/follows", %{"uri" => other_user.nickname})
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/follows", %{"uri" => other_user.nickname})
|
||||
|
||||
assert %{"id" => id} = json_response(conn, 200)
|
||||
assert id == to_string(other_user.id)
|
||||
|
@ -407,16 +482,19 @@ test "blocking / unblocking a user", %{conn: conn} do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/accounts/#{other_user.id}/block")
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/accounts/#{other_user.id}/block")
|
||||
|
||||
assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
conn = build_conn()
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/accounts/#{other_user.id}/unblock")
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/accounts/#{other_user.id}/unblock")
|
||||
|
||||
assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
|
||||
end
|
||||
|
@ -427,9 +505,10 @@ test "getting a list of blocks", %{conn: conn} do
|
|||
|
||||
{:ok, user} = User.block(user, other_user)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/blocks")
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/blocks")
|
||||
|
||||
other_user_id = to_string(other_user.id)
|
||||
assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
|
||||
|
@ -440,10 +519,11 @@ test "unimplemented mute endpoints" do
|
|||
other_user = insert(:user)
|
||||
|
||||
["mute", "unmute"]
|
||||
|> Enum.each(fn(endpoint) ->
|
||||
conn = build_conn()
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/accounts/#{other_user.id}/#{endpoint}")
|
||||
|> Enum.each(fn endpoint ->
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/accounts/#{other_user.id}/#{endpoint}")
|
||||
|
||||
assert %{"id" => id} = json_response(conn, 200)
|
||||
assert id == to_string(other_user.id)
|
||||
|
@ -454,10 +534,11 @@ test "unimplemented mutes, follow_requests, blocks, domain blocks" do
|
|||
user = insert(:user)
|
||||
|
||||
["blocks", "domain_blocks", "mutes", "follow_requests"]
|
||||
|> Enum.each(fn(endpoint) ->
|
||||
conn = build_conn()
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/#{endpoint}")
|
||||
|> Enum.each(fn endpoint ->
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/#{endpoint}")
|
||||
|
||||
assert [] = json_response(conn, 200)
|
||||
end)
|
||||
|
@ -468,9 +549,10 @@ test "account search", %{conn: conn} do
|
|||
_user_two = insert(:user, %{nickname: "shp@shitposter.club"})
|
||||
user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/accounts/search", %{"q" => "2hu"})
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/accounts/search", %{"q" => "2hu"})
|
||||
|
||||
assert [account] = json_response(conn, 200)
|
||||
assert account["id"] == to_string(user_three.id)
|
||||
|
@ -484,8 +566,9 @@ test "search", %{conn: conn} do
|
|||
{:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"})
|
||||
{:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
|
||||
|
||||
conn = conn
|
||||
|> get("/api/v1/search", %{"q" => "2hu"})
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/search", %{"q" => "2hu"})
|
||||
|
||||
assert results = json_response(conn, 200)
|
||||
|
||||
|
@ -499,19 +582,22 @@ test "search", %{conn: conn} do
|
|||
end
|
||||
|
||||
test "search fetches remote statuses", %{conn: conn} do
|
||||
capture_log fn ->
|
||||
conn = conn
|
||||
|> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
|
||||
capture_log(fn ->
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
|
||||
|
||||
assert results = json_response(conn, 200)
|
||||
|
||||
[status] = results["statuses"]
|
||||
assert status["uri"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
test "search fetches remote accounts", %{conn: conn} do
|
||||
conn = conn
|
||||
|> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"})
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"})
|
||||
|
||||
assert results = json_response(conn, 200)
|
||||
[account] = results["accounts"]
|
||||
|
@ -527,9 +613,10 @@ test "returns the favorites of a user", %{conn: conn} do
|
|||
|
||||
{:ok, _, _} = CommonAPI.favorite(activity.id, user)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/favourites")
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/favourites")
|
||||
|
||||
assert [status] = json_response(conn, 200)
|
||||
assert status["id"] == to_string(activity.id)
|
||||
|
@ -539,9 +626,10 @@ test "returns the favorites of a user", %{conn: conn} do
|
|||
test "updates the user's bio", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> patch("/api/v1/accounts/update_credentials", %{"note" => "I drink #cofe"})
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> patch("/api/v1/accounts/update_credentials", %{"note" => "I drink #cofe"})
|
||||
|
||||
assert user = json_response(conn, 200)
|
||||
assert user["note"] == "I drink #cofe"
|
||||
|
@ -550,9 +638,10 @@ test "updates the user's bio", %{conn: conn} do
|
|||
test "updates the user's name", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> patch("/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> patch("/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
|
||||
|
||||
assert user = json_response(conn, 200)
|
||||
assert user["display_name"] == "markorepairs"
|
||||
|
@ -561,11 +650,16 @@ test "updates the user's name", %{conn: conn} do
|
|||
test "updates the user's avatar", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
new_avatar = %Plug.Upload{content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg"}
|
||||
new_avatar = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an_image.jpg"
|
||||
}
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
|
||||
|
||||
assert user = json_response(conn, 200)
|
||||
assert user["avatar"] != "https://placehold.it/48x48"
|
||||
|
@ -574,11 +668,16 @@ test "updates the user's avatar", %{conn: conn} do
|
|||
test "updates the user's banner", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
new_header = %Plug.Upload{content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg"}
|
||||
new_header = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an_image.jpg"
|
||||
}
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> patch("/api/v1/accounts/update_credentials", %{"header" => new_header})
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> patch("/api/v1/accounts/update_credentials", %{"header" => new_header})
|
||||
|
||||
assert user = json_response(conn, 200)
|
||||
assert user["header"] != "https://placehold.it/700x335"
|
||||
|
@ -594,8 +693,9 @@ test "get instance information", %{conn: conn} do
|
|||
|
||||
Pleroma.Stats.update_stats()
|
||||
|
||||
conn = conn
|
||||
|> get("/api/v1/instance")
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/instance")
|
||||
|
||||
assert result = json_response(conn, 200)
|
||||
|
||||
|
|
|
@ -13,8 +13,9 @@ test "a note activity" do
|
|||
|
||||
status = StatusView.render("status.json", %{activity: note})
|
||||
|
||||
created_at = (note.data["object"]["published"] || "")
|
||||
|> String.replace(~r/\.\d+Z/, ".000Z")
|
||||
created_at =
|
||||
(note.data["object"]["published"] || "")
|
||||
|> String.replace(~r/\.\d+Z/, ".000Z")
|
||||
|
||||
expected = %{
|
||||
id: to_string(note.id),
|
||||
|
@ -57,7 +58,9 @@ test "a note activity" do
|
|||
test "a reply" do
|
||||
note = insert(:note_activity)
|
||||
user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "he", "in_reply_to_status_id" => note.id})
|
||||
|
||||
{:ok, activity} =
|
||||
CommonAPI.post(user, %{"status" => "he", "in_reply_to_status_id" => note.id})
|
||||
|
||||
status = StatusView.render("status.json", %{activity: activity})
|
||||
|
||||
|
|
|
@ -4,7 +4,15 @@ defmodule Pleroma.Web.OAuth.AuthorizationTest do
|
|||
import Pleroma.Factory
|
||||
|
||||
test "create an authorization token for a valid app" do
|
||||
{:ok, app} = Repo.insert(App.register_changeset(%App{}, %{client_name: "client", scopes: "scope", redirect_uris: "url"}))
|
||||
{:ok, app} =
|
||||
Repo.insert(
|
||||
App.register_changeset(%App{}, %{
|
||||
client_name: "client",
|
||||
scopes: "scope",
|
||||
redirect_uris: "url"
|
||||
})
|
||||
)
|
||||
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, auth} = Authorization.create_authorization(app, user)
|
||||
|
@ -16,7 +24,15 @@ test "create an authorization token for a valid app" do
|
|||
end
|
||||
|
||||
test "use up a token" do
|
||||
{:ok, app} = Repo.insert(App.register_changeset(%App{}, %{client_name: "client", scopes: "scope", redirect_uris: "url"}))
|
||||
{:ok, app} =
|
||||
Repo.insert(
|
||||
App.register_changeset(%App{}, %{
|
||||
client_name: "client",
|
||||
scopes: "scope",
|
||||
redirect_uris: "url"
|
||||
})
|
||||
)
|
||||
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, auth} = Authorization.create_authorization(app, user)
|
||||
|
@ -30,7 +46,7 @@ test "use up a token" do
|
|||
expired_auth = %Authorization{
|
||||
user_id: user.id,
|
||||
app_id: app.id,
|
||||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now, -10),
|
||||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), -10),
|
||||
token: "mytoken",
|
||||
used: false
|
||||
}
|
||||
|
|
|
@ -6,7 +6,15 @@ defmodule Pleroma.Web.OAuth.TokenTest do
|
|||
import Pleroma.Factory
|
||||
|
||||
test "exchanges a auth token for an access token" do
|
||||
{:ok, app} = Repo.insert(App.register_changeset(%App{}, %{client_name: "client", scopes: "scope", redirect_uris: "url"}))
|
||||
{:ok, app} =
|
||||
Repo.insert(
|
||||
App.register_changeset(%App{}, %{
|
||||
client_name: "client",
|
||||
scopes: "scope",
|
||||
redirect_uris: "url"
|
||||
})
|
||||
)
|
||||
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, auth} = Authorization.create_authorization(app, user)
|
||||
|
|
|
@ -16,9 +16,12 @@ test "an external note activity" do
|
|||
|
||||
tuple = ActivityRepresenter.to_simple_form(activity, user)
|
||||
|
||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary
|
||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary()
|
||||
|
||||
assert String.contains?(res, ~s{<link type="text/html" href="https://mastodon.social/users/lambadalambda/updates/2314748" rel="alternate"/>})
|
||||
assert String.contains?(
|
||||
res,
|
||||
~s{<link type="text/html" href="https://mastodon.social/users/lambadalambda/updates/2314748" rel="alternate"/>}
|
||||
)
|
||||
end
|
||||
|
||||
test "a note activity" do
|
||||
|
@ -46,7 +49,7 @@ test "a note activity" do
|
|||
|
||||
tuple = ActivityRepresenter.to_simple_form(note_activity, user)
|
||||
|
||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary
|
||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary()
|
||||
|
||||
assert clean(res) == clean(expected)
|
||||
end
|
||||
|
@ -61,7 +64,10 @@ test "a reply note" do
|
|||
answer = %{answer | data: data}
|
||||
|
||||
note_object = Object.get_by_ap_id(note.data["object"]["id"])
|
||||
Repo.update!(Object.change(note_object, %{ data: Map.put(note_object.data, "external_url", "someurl") }))
|
||||
|
||||
Repo.update!(
|
||||
Object.change(note_object, %{data: Map.put(note_object.data, "external_url", "someurl")})
|
||||
)
|
||||
|
||||
user = User.get_cached_by_ap_id(answer.data["actor"])
|
||||
|
||||
|
@ -86,7 +92,7 @@ test "a reply note" do
|
|||
|
||||
tuple = ActivityRepresenter.to_simple_form(answer, user)
|
||||
|
||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary
|
||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary()
|
||||
|
||||
assert clean(res) == clean(expected)
|
||||
end
|
||||
|
@ -102,9 +108,11 @@ test "an announce activity" do
|
|||
|
||||
note_user = User.get_cached_by_ap_id(note.data["actor"])
|
||||
note = Repo.get(Activity, note.id)
|
||||
note_xml = ActivityRepresenter.to_simple_form(note, note_user, true)
|
||||
|> :xmerl.export_simple_content(:xmerl_xml)
|
||||
|> to_string
|
||||
|
||||
note_xml =
|
||||
ActivityRepresenter.to_simple_form(note, note_user, true)
|
||||
|> :xmerl.export_simple_content(:xmerl_xml)
|
||||
|> to_string
|
||||
|
||||
expected = """
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
|
||||
|
@ -120,13 +128,16 @@ test "an announce activity" do
|
|||
<activity:object>
|
||||
#{note_xml}
|
||||
</activity:object>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{note.data["actor"]}"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{
|
||||
note.data["actor"]
|
||||
}"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
"""
|
||||
|
||||
announce_xml = ActivityRepresenter.to_simple_form(announce, user)
|
||||
|> :xmerl.export_simple_content(:xmerl_xml)
|
||||
|> to_string
|
||||
announce_xml =
|
||||
ActivityRepresenter.to_simple_form(announce, user)
|
||||
|> :xmerl.export_simple_content(:xmerl_xml)
|
||||
|> to_string
|
||||
|
||||
assert clean(expected) == clean(announce_xml)
|
||||
end
|
||||
|
@ -139,7 +150,7 @@ test "a like activity" do
|
|||
tuple = ActivityRepresenter.to_simple_form(like, user)
|
||||
refute is_nil(tuple)
|
||||
|
||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary
|
||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary()
|
||||
|
||||
expected = """
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/favorite</activity:verb>
|
||||
|
@ -156,7 +167,9 @@ test "a like activity" do
|
|||
<link ref="#{like.data["context"]}" rel="ostatus:conversation" />
|
||||
<link rel="self" type="application/atom+xml" href="#{like.data["id"]}"/>
|
||||
<thr:in-reply-to ref="#{note.data["id"]}" />
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{note.data["actor"]}"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{
|
||||
note.data["actor"]
|
||||
}"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
"""
|
||||
|
||||
|
@ -166,18 +179,20 @@ test "a like activity" do
|
|||
test "a follow activity" do
|
||||
follower = insert(:user)
|
||||
followed = insert(:user)
|
||||
{:ok, activity} = ActivityPub.insert(%{
|
||||
"type" => "Follow",
|
||||
"actor" => follower.ap_id,
|
||||
"object" => followed.ap_id,
|
||||
"to" => [followed.ap_id]
|
||||
})
|
||||
|
||||
{:ok, activity} =
|
||||
ActivityPub.insert(%{
|
||||
"type" => "Follow",
|
||||
"actor" => follower.ap_id,
|
||||
"object" => followed.ap_id,
|
||||
"to" => [followed.ap_id]
|
||||
})
|
||||
|
||||
tuple = ActivityRepresenter.to_simple_form(activity, follower)
|
||||
|
||||
refute is_nil(tuple)
|
||||
|
||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary
|
||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary()
|
||||
|
||||
expected = """
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
|
||||
|
@ -193,7 +208,9 @@ test "a follow activity" do
|
|||
<uri>#{activity.data["object"]}</uri>
|
||||
</activity:object>
|
||||
<link rel="self" type="application/atom+xml" href="#{activity.data["id"]}"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{activity.data["object"]}"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{
|
||||
activity.data["object"]
|
||||
}"/>
|
||||
"""
|
||||
|
||||
assert clean(res) == clean(expected)
|
||||
|
@ -209,7 +226,7 @@ test "an unfollow activity" do
|
|||
|
||||
refute is_nil(tuple)
|
||||
|
||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary
|
||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary()
|
||||
|
||||
expected = """
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
|
||||
|
@ -225,7 +242,9 @@ test "an unfollow activity" do
|
|||
<uri>#{followed.ap_id}</uri>
|
||||
</activity:object>
|
||||
<link rel="self" type="application/atom+xml" href="#{activity.data["id"]}"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{followed.ap_id}"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{
|
||||
followed.ap_id
|
||||
}"/>
|
||||
"""
|
||||
|
||||
assert clean(res) == clean(expected)
|
||||
|
@ -233,13 +252,22 @@ test "an unfollow activity" do
|
|||
|
||||
test "a delete" do
|
||||
user = insert(:user)
|
||||
activity = %Activity{data: %{ "id" => "ap_id", "type" => "Delete", "actor" => user.ap_id, "object" => "some_id", "published" => "2017-06-18T12:00:18+00:00" }}
|
||||
|
||||
activity = %Activity{
|
||||
data: %{
|
||||
"id" => "ap_id",
|
||||
"type" => "Delete",
|
||||
"actor" => user.ap_id,
|
||||
"object" => "some_id",
|
||||
"published" => "2017-06-18T12:00:18+00:00"
|
||||
}
|
||||
}
|
||||
|
||||
tuple = ActivityRepresenter.to_simple_form(activity, nil)
|
||||
|
||||
refute is_nil(tuple)
|
||||
|
||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary
|
||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary()
|
||||
|
||||
expected = """
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
|
||||
|
|
|
@ -11,15 +11,19 @@ test "returns a feed of the last 20 items of the user" do
|
|||
|
||||
tuple = FeedRepresenter.to_simple_form(user, [note_activity], [user])
|
||||
|
||||
most_recent_update = note_activity.updated_at
|
||||
|> NaiveDateTime.to_iso8601
|
||||
most_recent_update =
|
||||
note_activity.updated_at
|
||||
|> NaiveDateTime.to_iso8601()
|
||||
|
||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> to_string
|
||||
user_xml = UserRepresenter.to_simple_form(user)
|
||||
|> :xmerl.export_simple_content(:xmerl_xml)
|
||||
|
||||
entry_xml = ActivityRepresenter.to_simple_form(note_activity, user)
|
||||
|> :xmerl.export_simple_content(:xmerl_xml)
|
||||
user_xml =
|
||||
UserRepresenter.to_simple_form(user)
|
||||
|> :xmerl.export_simple_content(:xmerl_xml)
|
||||
|
||||
entry_xml =
|
||||
ActivityRepresenter.to_simple_form(note_activity, user)
|
||||
|> :xmerl.export_simple_content(:xmerl_xml)
|
||||
|
||||
expected = """
|
||||
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0">
|
||||
|
@ -39,6 +43,7 @@ test "returns a feed of the last 20 items of the user" do
|
|||
</entry>
|
||||
</feed>
|
||||
"""
|
||||
|
||||
assert clean(res) == clean(expected)
|
||||
end
|
||||
|
||||
|
|
|
@ -14,8 +14,13 @@ test "it removes the mentioned activity" do
|
|||
|
||||
{:ok, like, _object} = Pleroma.Web.ActivityPub.ActivityPub.like(user, object)
|
||||
|
||||
incoming = File.read!("test/fixtures/delete.xml")
|
||||
|> String.replace("tag:mastodon.sdf.org,2017-06-10:objectId=310513:objectType=Status", note.data["object"]["id"])
|
||||
incoming =
|
||||
File.read!("test/fixtures/delete.xml")
|
||||
|> String.replace(
|
||||
"tag:mastodon.sdf.org,2017-06-10:objectId=310513:objectType=Status",
|
||||
note.data["object"]["id"]
|
||||
)
|
||||
|
||||
{:ok, [delete]} = OStatus.handle_incoming(incoming)
|
||||
|
||||
refute Repo.get(Activity, note.id)
|
||||
|
|
|
@ -7,9 +7,11 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
|
|||
test "decodes a salmon", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
salmon = File.read!("test/fixtures/salmon.xml")
|
||||
conn = conn
|
||||
|> put_req_header("content-type", "application/atom+xml")
|
||||
|> post("/users/#{user.nickname}/salmon", salmon)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/atom+xml")
|
||||
|> post("/users/#{user.nickname}/salmon", salmon)
|
||||
|
||||
assert response(conn, 200)
|
||||
end
|
||||
|
@ -17,21 +19,30 @@ test "decodes a salmon", %{conn: conn} do
|
|||
test "decodes a salmon with a changed magic key", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
salmon = File.read!("test/fixtures/salmon.xml")
|
||||
conn = conn
|
||||
|> put_req_header("content-type", "application/atom+xml")
|
||||
|> post("/users/#{user.nickname}/salmon", salmon)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/atom+xml")
|
||||
|> post("/users/#{user.nickname}/salmon", salmon)
|
||||
|
||||
assert response(conn, 200)
|
||||
|
||||
# Set a wrong magic-key for a user so it has to refetch
|
||||
salmon_user = User.get_by_ap_id("http://gs.example.org:4040/index.php/user/1")
|
||||
info = salmon_user.info
|
||||
|> Map.put("magic_key", "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwrong1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB") # Wrong key
|
||||
# Wrong key
|
||||
info =
|
||||
salmon_user.info
|
||||
|> Map.put(
|
||||
"magic_key",
|
||||
"RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwrong1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB"
|
||||
)
|
||||
|
||||
Repo.update(User.info_changeset(salmon_user, %{info: info}))
|
||||
|
||||
conn = build_conn()
|
||||
|> put_req_header("content-type", "application/atom+xml")
|
||||
|> post("/users/#{user.nickname}/salmon", salmon)
|
||||
conn =
|
||||
build_conn()
|
||||
|> put_req_header("content-type", "application/atom+xml")
|
||||
|> post("/users/#{user.nickname}/salmon", salmon)
|
||||
|
||||
assert response(conn, 200)
|
||||
end
|
||||
|
@ -40,8 +51,9 @@ test "gets a feed", %{conn: conn} do
|
|||
note_activity = insert(:note_activity)
|
||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||
|
||||
conn = conn
|
||||
|> get("/users/#{user.nickname}/feed.atom")
|
||||
conn =
|
||||
conn
|
||||
|> get("/users/#{user.nickname}/feed.atom")
|
||||
|
||||
assert response(conn, 200) =~ note_activity.data["object"]["content"]
|
||||
end
|
||||
|
@ -49,27 +61,30 @@ test "gets a feed", %{conn: conn} do
|
|||
test "gets an object", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
user = User.get_by_ap_id(note_activity.data["actor"])
|
||||
[_, uuid] = hd Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"])
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"]))
|
||||
url = "/objects/#{uuid}"
|
||||
|
||||
conn = conn
|
||||
|> get(url)
|
||||
conn =
|
||||
conn
|
||||
|> get(url)
|
||||
|
||||
expected = ActivityRepresenter.to_simple_form(note_activity, user, true)
|
||||
|> ActivityRepresenter.wrap_with_entry
|
||||
|> :xmerl.export_simple(:xmerl_xml)
|
||||
|> to_string
|
||||
expected =
|
||||
ActivityRepresenter.to_simple_form(note_activity, user, true)
|
||||
|> ActivityRepresenter.wrap_with_entry()
|
||||
|> :xmerl.export_simple(:xmerl_xml)
|
||||
|> to_string
|
||||
|
||||
assert response(conn, 200) == expected
|
||||
end
|
||||
|
||||
test "gets an activity", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
[_, uuid] = hd Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
|
||||
url = "/activities/#{uuid}"
|
||||
|
||||
conn = conn
|
||||
|> get(url)
|
||||
conn =
|
||||
conn
|
||||
|> get(url)
|
||||
|
||||
assert response(conn, 200)
|
||||
end
|
||||
|
@ -78,8 +93,9 @@ test "gets a notice", %{conn: conn} do
|
|||
note_activity = insert(:note_activity)
|
||||
url = "/notice/#{note_activity.id}"
|
||||
|
||||
conn = conn
|
||||
|> get(url)
|
||||
conn =
|
||||
conn
|
||||
|> get(url)
|
||||
|
||||
assert response(conn, 200)
|
||||
end
|
||||
|
|
|
@ -20,12 +20,18 @@ test "handle incoming note - GS, Salmon" do
|
|||
assert user.info["note_count"] == 1
|
||||
assert activity.data["type"] == "Create"
|
||||
assert activity.data["object"]["type"] == "Note"
|
||||
assert activity.data["object"]["id"] == "tag:gs.example.org:4040,2017-04-23:noticeId=29:objectType=note"
|
||||
|
||||
assert activity.data["object"]["id"] ==
|
||||
"tag:gs.example.org:4040,2017-04-23:noticeId=29:objectType=note"
|
||||
|
||||
assert activity.data["published"] == "2017-04-23T14:51:03+00:00"
|
||||
assert activity.data["object"]["published"] == "2017-04-23T14:51:03+00:00"
|
||||
assert activity.data["context"] == "tag:gs.example.org:4040,2017-04-23:objectType=thread:nonce=f09e22f58abd5c7b"
|
||||
|
||||
assert activity.data["context"] ==
|
||||
"tag:gs.example.org:4040,2017-04-23:objectType=thread:nonce=f09e22f58abd5c7b"
|
||||
|
||||
assert "http://pleroma.example.org:4000/users/lain3" in activity.data["to"]
|
||||
assert activity.data["object"]["emoji"] == %{ "marko" => "marko.png", "reimu" => "reimu.png" }
|
||||
assert activity.data["object"]["emoji"] == %{"marko" => "marko.png", "reimu" => "reimu.png"}
|
||||
assert activity.local == false
|
||||
end
|
||||
|
||||
|
@ -65,10 +71,12 @@ test "handle incoming notes with tags" do
|
|||
test "handle incoming notes - Mastodon, salmon, reply" do
|
||||
# It uses the context of the replied to object
|
||||
Repo.insert!(%Object{
|
||||
data: %{
|
||||
"id" => "https://pleroma.soykaf.com/objects/c237d966-ac75-4fe3-a87a-d89d71a3a7a4",
|
||||
"context" => "2hu"
|
||||
}})
|
||||
data: %{
|
||||
"id" => "https://pleroma.soykaf.com/objects/c237d966-ac75-4fe3-a87a-d89d71a3a7a4",
|
||||
"context" => "2hu"
|
||||
}
|
||||
})
|
||||
|
||||
incoming = File.read!("test/fixtures/incoming_reply_mastodon.xml")
|
||||
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
||||
|
||||
|
@ -113,8 +121,13 @@ test "handle incoming notes - GS, subscription, reply" do
|
|||
assert activity.data["type"] == "Create"
|
||||
assert activity.data["object"]["type"] == "Note"
|
||||
assert activity.data["object"]["actor"] == "https://social.heldscal.la/user/23211"
|
||||
assert activity.data["object"]["content"] == "@<a href=\"https://gs.archae.me/user/4687\" class=\"h-card u-url p-nickname mention\" title=\"shpbot\">shpbot</a> why not indeed."
|
||||
assert activity.data["object"]["inReplyTo"] == "tag:gs.archae.me,2017-04-30:noticeId=778260:objectType=note"
|
||||
|
||||
assert activity.data["object"]["content"] ==
|
||||
"@<a href=\"https://gs.archae.me/user/4687\" class=\"h-card u-url p-nickname mention\" title=\"shpbot\">shpbot</a> why not indeed."
|
||||
|
||||
assert activity.data["object"]["inReplyTo"] ==
|
||||
"tag:gs.archae.me,2017-04-30:noticeId=778260:objectType=note"
|
||||
|
||||
assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
|
||||
end
|
||||
|
||||
|
@ -141,9 +154,11 @@ test "handle incoming retweets - GS, subscription - local message" do
|
|||
incoming = File.read!("test/fixtures/share-gs-local.xml")
|
||||
note_activity = insert(:note_activity)
|
||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||
incoming = incoming
|
||||
|> String.replace("LOCAL_ID", note_activity.data["object"]["id"])
|
||||
|> String.replace("LOCAL_USER", user.ap_id)
|
||||
|
||||
incoming =
|
||||
incoming
|
||||
|> String.replace("LOCAL_ID", note_activity.data["object"]["id"])
|
||||
|> String.replace("LOCAL_USER", user.ap_id)
|
||||
|
||||
{:ok, [[activity, retweeted_activity]]} = OStatus.handle_incoming(incoming)
|
||||
|
||||
|
@ -168,7 +183,9 @@ test "handle incoming retweets - Mastodon, salmon" do
|
|||
assert activity.data["type"] == "Announce"
|
||||
assert activity.data["actor"] == "https://mastodon.social/users/lambadalambda"
|
||||
assert activity.data["object"] == retweeted_activity.data["object"]["id"]
|
||||
assert activity.data["id"] == "tag:mastodon.social,2017-05-03:objectId=4934452:objectType=Status"
|
||||
|
||||
assert activity.data["id"] ==
|
||||
"tag:mastodon.social,2017-05-03:objectId=4934452:objectType=Status"
|
||||
|
||||
refute activity.local
|
||||
assert retweeted_activity.data["type"] == "Create"
|
||||
|
@ -178,35 +195,42 @@ test "handle incoming retweets - Mastodon, salmon" do
|
|||
end
|
||||
|
||||
test "handle incoming favorites - GS, websub" do
|
||||
capture_log fn ->
|
||||
capture_log(fn ->
|
||||
incoming = File.read!("test/fixtures/favorite.xml")
|
||||
{:ok, [[activity, favorited_activity]]} = OStatus.handle_incoming(incoming)
|
||||
|
||||
assert activity.data["type"] == "Like"
|
||||
assert activity.data["actor"] == "https://social.heldscal.la/user/23211"
|
||||
assert activity.data["object"] == favorited_activity.data["object"]["id"]
|
||||
assert activity.data["id"] == "tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061643:2017-05-05T09:12:50+00:00"
|
||||
|
||||
assert activity.data["id"] ==
|
||||
"tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061643:2017-05-05T09:12:50+00:00"
|
||||
|
||||
refute activity.local
|
||||
assert favorited_activity.data["type"] == "Create"
|
||||
assert favorited_activity.data["actor"] == "https://shitposter.club/user/1"
|
||||
assert favorited_activity.data["object"]["id"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
|
||||
|
||||
assert favorited_activity.data["object"]["id"] ==
|
||||
"tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
|
||||
|
||||
refute favorited_activity.local
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
test "handle conversation references" do
|
||||
incoming = File.read!("test/fixtures/mastodon_conversation.xml")
|
||||
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
||||
|
||||
assert activity.data["context"] == "tag:mastodon.social,2017-08-28:objectId=7876885:objectType=Conversation"
|
||||
assert activity.data["context"] ==
|
||||
"tag:mastodon.social,2017-08-28:objectId=7876885:objectType=Conversation"
|
||||
end
|
||||
|
||||
test "handle incoming favorites with locally available object - GS, websub" do
|
||||
note_activity = insert(:note_activity)
|
||||
|
||||
incoming = File.read!("test/fixtures/favorite_with_local_note.xml")
|
||||
|> String.replace("localid", note_activity.data["object"]["id"])
|
||||
incoming =
|
||||
File.read!("test/fixtures/favorite_with_local_note.xml")
|
||||
|> String.replace("localid", note_activity.data["object"]["id"])
|
||||
|
||||
{:ok, [[activity, favorited_activity]]} = OStatus.handle_incoming(incoming)
|
||||
|
||||
|
@ -224,9 +248,15 @@ test "handle incoming replies" do
|
|||
|
||||
assert activity.data["type"] == "Create"
|
||||
assert activity.data["object"]["type"] == "Note"
|
||||
assert activity.data["object"]["inReplyTo"] == "http://pleroma.example.org:4000/objects/55bce8fc-b423-46b1-af71-3759ab4670bc"
|
||||
|
||||
assert activity.data["object"]["inReplyTo"] ==
|
||||
"http://pleroma.example.org:4000/objects/55bce8fc-b423-46b1-af71-3759ab4670bc"
|
||||
|
||||
assert "http://pleroma.example.org:4000/users/lain5" in activity.data["to"]
|
||||
assert activity.data["object"]["id"] == "tag:gs.example.org:4040,2017-04-25:noticeId=55:objectType=note"
|
||||
|
||||
assert activity.data["object"]["id"] ==
|
||||
"tag:gs.example.org:4040,2017-04-25:noticeId=55:objectType=note"
|
||||
|
||||
assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
|
||||
end
|
||||
|
||||
|
@ -234,7 +264,10 @@ test "handle incoming follows" do
|
|||
incoming = File.read!("test/fixtures/follow.xml")
|
||||
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
||||
assert activity.data["type"] == "Follow"
|
||||
assert activity.data["id"] == "tag:social.heldscal.la,2017-05-07:subscription:23211:person:44803:2017-05-07T09:54:48+00:00"
|
||||
|
||||
assert activity.data["id"] ==
|
||||
"tag:social.heldscal.la,2017-05-07:subscription:23211:person:44803:2017-05-07T09:54:48+00:00"
|
||||
|
||||
assert activity.data["actor"] == "https://social.heldscal.la/user/23211"
|
||||
assert activity.data["object"] == "https://pawoo.net/users/pekorino"
|
||||
refute activity.local
|
||||
|
@ -304,7 +337,8 @@ test "it returns user info in a hash" do
|
|||
|
||||
expected = %{
|
||||
"hub" => "https://social.heldscal.la/main/push/hub",
|
||||
"magic_key" => "RSA.wQ3i9UA0qmAxZ0WTIp4a-waZn_17Ez1pEEmqmqoooRsG1_BvpmOvLN0G2tEcWWxl2KOtdQMCiPptmQObeZeuj48mdsDZ4ArQinexY2hCCTcbV8Xpswpkb8K05RcKipdg07pnI7tAgQ0VWSZDImncL6YUGlG5YN8b5TjGOwk2VG8=.AQAB",
|
||||
"magic_key" =>
|
||||
"RSA.wQ3i9UA0qmAxZ0WTIp4a-waZn_17Ez1pEEmqmqoooRsG1_BvpmOvLN0G2tEcWWxl2KOtdQMCiPptmQObeZeuj48mdsDZ4ArQinexY2hCCTcbV8Xpswpkb8K05RcKipdg07pnI7tAgQ0VWSZDImncL6YUGlG5YN8b5TjGOwk2VG8=.AQAB",
|
||||
"name" => "shp",
|
||||
"nickname" => "shp",
|
||||
"salmon" => "https://social.heldscal.la/main/salmon/user/29191",
|
||||
|
@ -314,10 +348,20 @@ test "it returns user info in a hash" do
|
|||
"host" => "social.heldscal.la",
|
||||
"fqn" => user,
|
||||
"bio" => "cofe",
|
||||
"avatar" => %{"type" => "Image", "url" => [%{"href" => "https://social.heldscal.la/avatar/29191-original-20170421154949.jpeg", "mediaType" => "image/jpeg", "type" => "Link"}]},
|
||||
"avatar" => %{
|
||||
"type" => "Image",
|
||||
"url" => [
|
||||
%{
|
||||
"href" => "https://social.heldscal.la/avatar/29191-original-20170421154949.jpeg",
|
||||
"mediaType" => "image/jpeg",
|
||||
"type" => "Link"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subscribe_address" => "https://social.heldscal.la/main/ostatussub?profile={uri}",
|
||||
"ap_id" => nil
|
||||
}
|
||||
|
||||
assert data == expected
|
||||
end
|
||||
|
||||
|
@ -329,7 +373,8 @@ test "it works with the uri" do
|
|||
|
||||
expected = %{
|
||||
"hub" => "https://social.heldscal.la/main/push/hub",
|
||||
"magic_key" => "RSA.wQ3i9UA0qmAxZ0WTIp4a-waZn_17Ez1pEEmqmqoooRsG1_BvpmOvLN0G2tEcWWxl2KOtdQMCiPptmQObeZeuj48mdsDZ4ArQinexY2hCCTcbV8Xpswpkb8K05RcKipdg07pnI7tAgQ0VWSZDImncL6YUGlG5YN8b5TjGOwk2VG8=.AQAB",
|
||||
"magic_key" =>
|
||||
"RSA.wQ3i9UA0qmAxZ0WTIp4a-waZn_17Ez1pEEmqmqoooRsG1_BvpmOvLN0G2tEcWWxl2KOtdQMCiPptmQObeZeuj48mdsDZ4ArQinexY2hCCTcbV8Xpswpkb8K05RcKipdg07pnI7tAgQ0VWSZDImncL6YUGlG5YN8b5TjGOwk2VG8=.AQAB",
|
||||
"name" => "shp",
|
||||
"nickname" => "shp",
|
||||
"salmon" => "https://social.heldscal.la/main/salmon/user/29191",
|
||||
|
@ -339,28 +384,40 @@ test "it works with the uri" do
|
|||
"host" => "social.heldscal.la",
|
||||
"fqn" => user,
|
||||
"bio" => "cofe",
|
||||
"avatar" => %{"type" => "Image", "url" => [%{"href" => "https://social.heldscal.la/avatar/29191-original-20170421154949.jpeg", "mediaType" => "image/jpeg", "type" => "Link"}]},
|
||||
"avatar" => %{
|
||||
"type" => "Image",
|
||||
"url" => [
|
||||
%{
|
||||
"href" => "https://social.heldscal.la/avatar/29191-original-20170421154949.jpeg",
|
||||
"mediaType" => "image/jpeg",
|
||||
"type" => "Link"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subscribe_address" => "https://social.heldscal.la/main/ostatussub?profile={uri}",
|
||||
"ap_id" => nil
|
||||
}
|
||||
|
||||
assert data == expected
|
||||
end
|
||||
end
|
||||
|
||||
describe "fetching a status by it's HTML url" do
|
||||
test "it builds a missing status from an html url" do
|
||||
capture_log fn ->
|
||||
capture_log(fn ->
|
||||
url = "https://shitposter.club/notice/2827873"
|
||||
{:ok, [activity] } = OStatus.fetch_activity_from_url(url)
|
||||
{:ok, [activity]} = OStatus.fetch_activity_from_url(url)
|
||||
|
||||
assert activity.data["actor"] == "https://shitposter.club/user/1"
|
||||
assert activity.data["object"]["id"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
|
||||
end
|
||||
|
||||
assert activity.data["object"]["id"] ==
|
||||
"tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
|
||||
end)
|
||||
end
|
||||
|
||||
test "it works for atom notes, too" do
|
||||
url = "https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056"
|
||||
{:ok, [activity] } = OStatus.fetch_activity_from_url(url)
|
||||
{:ok, [activity]} = OStatus.fetch_activity_from_url(url)
|
||||
assert activity.data["actor"] == "https://social.sakamoto.gq/users/eal"
|
||||
assert activity.data["object"]["id"] == url
|
||||
end
|
||||
|
@ -370,6 +427,9 @@ test "it doesn't add nil in the do field" do
|
|||
incoming = File.read!("test/fixtures/nil_mention_entry.xml")
|
||||
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
||||
|
||||
assert activity.data["to"] == ["http://localhost:4001/users/atarifrosch@social.stopwatchingus-heidelberg.de/followers", "https://www.w3.org/ns/activitystreams#Public"]
|
||||
assert activity.data["to"] == [
|
||||
"http://localhost:4001/users/atarifrosch@social.stopwatchingus-heidelberg.de/followers",
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue