Format the code.
This commit is contained in:
parent
480932c8e5
commit
4afbef39f4
|
@ -6,14 +6,11 @@
|
||||||
use Mix.Config
|
use Mix.Config
|
||||||
|
|
||||||
# General application configuration
|
# General application configuration
|
||||||
config :pleroma,
|
config :pleroma, ecto_repos: [Pleroma.Repo]
|
||||||
ecto_repos: [Pleroma.Repo]
|
|
||||||
|
|
||||||
config :pleroma, Pleroma.Repo,
|
config :pleroma, Pleroma.Repo, types: Pleroma.PostgresTypes
|
||||||
types: Pleroma.PostgresTypes
|
|
||||||
|
|
||||||
config :pleroma, Pleroma.Upload,
|
config :pleroma, Pleroma.Upload, uploads: "uploads"
|
||||||
uploads: "uploads"
|
|
||||||
|
|
||||||
# Configures the endpoint
|
# Configures the endpoint
|
||||||
config :pleroma, Pleroma.Web.Endpoint,
|
config :pleroma, Pleroma.Web.Endpoint,
|
||||||
|
@ -21,8 +18,7 @@
|
||||||
protocol: "https",
|
protocol: "https",
|
||||||
secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl",
|
secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl",
|
||||||
render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
|
render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
|
||||||
pubsub: [name: Pleroma.PubSub,
|
pubsub: [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]
|
||||||
adapter: Phoenix.PubSub.PG2]
|
|
||||||
|
|
||||||
# Configures Elixir's Logger
|
# Configures Elixir's Logger
|
||||||
config :logger, :console,
|
config :logger, :console,
|
||||||
|
@ -38,15 +34,15 @@
|
||||||
config :pleroma, :ostatus, Pleroma.Web.OStatus
|
config :pleroma, :ostatus, Pleroma.Web.OStatus
|
||||||
config :pleroma, :httpoison, Pleroma.HTTP
|
config :pleroma, :httpoison, Pleroma.HTTP
|
||||||
|
|
||||||
version = with {version, 0} <- System.cmd("git", ["rev-parse", "HEAD"]) do
|
version =
|
||||||
"Pleroma #{Mix.Project.config[:version]} #{String.trim(version)}"
|
with {version, 0} <- System.cmd("git", ["rev-parse", "HEAD"]) do
|
||||||
else
|
"Pleroma #{Mix.Project.config()[:version]} #{String.trim(version)}"
|
||||||
_ -> "Pleroma #{Mix.Project.config[:version]} dev"
|
else
|
||||||
end
|
_ -> "Pleroma #{Mix.Project.config()[:version]} dev"
|
||||||
|
end
|
||||||
|
|
||||||
# Configures http settings, upstream proxy etc.
|
# Configures http settings, upstream proxy etc.
|
||||||
config :pleroma, :http,
|
config :pleroma, :http, proxy_url: nil
|
||||||
proxy_url: nil
|
|
||||||
|
|
||||||
config :pleroma, :instance,
|
config :pleroma, :instance,
|
||||||
version: version,
|
version: version,
|
||||||
|
@ -59,16 +55,15 @@
|
||||||
config :pleroma, :media_proxy,
|
config :pleroma, :media_proxy,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
redirect_on_failure: true
|
redirect_on_failure: true
|
||||||
#base_url: "https://cache.pleroma.social"
|
|
||||||
|
|
||||||
config :pleroma, :chat,
|
# base_url: "https://cache.pleroma.social"
|
||||||
enabled: true
|
|
||||||
|
config :pleroma, :chat, enabled: true
|
||||||
|
|
||||||
config :ecto, json_library: Jason
|
config :ecto, json_library: Jason
|
||||||
|
|
||||||
config :phoenix, :format_encoders,
|
config :phoenix, :format_encoders, json: Jason
|
||||||
json: Jason
|
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
import_config "#{Mix.env}.exs"
|
import_config "#{Mix.env()}.exs"
|
||||||
|
|
|
@ -7,7 +7,10 @@
|
||||||
# watchers to your application. For example, we use it
|
# watchers to your application. For example, we use it
|
||||||
# with brunch.io to recompile .js and .css sources.
|
# with brunch.io to recompile .js and .css sources.
|
||||||
config :pleroma, Pleroma.Web.Endpoint,
|
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",
|
protocol: "http",
|
||||||
debug_errors: true,
|
debug_errors: true,
|
||||||
code_reloader: true,
|
code_reloader: true,
|
||||||
|
@ -49,5 +52,8 @@
|
||||||
try do
|
try do
|
||||||
import_config "dev.secret.exs"
|
import_config "dev.secret.exs"
|
||||||
rescue
|
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
|
end
|
||||||
|
|
|
@ -9,8 +9,7 @@
|
||||||
# Print only warnings and errors during test
|
# Print only warnings and errors during test
|
||||||
config :logger, level: :warn
|
config :logger, level: :warn
|
||||||
|
|
||||||
config :pleroma, Pleroma.Upload,
|
config :pleroma, Pleroma.Upload, uploads: "test/uploads"
|
||||||
uploads: "test/uploads"
|
|
||||||
|
|
||||||
# Configure your database
|
# Configure your database
|
||||||
config :pleroma, Pleroma.Repo,
|
config :pleroma, Pleroma.Repo,
|
||||||
|
@ -21,7 +20,6 @@
|
||||||
hostname: System.get_env("DB_HOST") || "localhost",
|
hostname: System.get_env("DB_HOST") || "localhost",
|
||||||
pool: Ecto.Adapters.SQL.Sandbox
|
pool: Ecto.Adapters.SQL.Sandbox
|
||||||
|
|
||||||
|
|
||||||
# Reduce hash rounds for testing
|
# Reduce hash rounds for testing
|
||||||
config :comeonin, :pbkdf2_rounds, 1
|
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
|
def run([]) do
|
||||||
Mix.Task.run("app.start")
|
Mix.Task.run("app.start")
|
||||||
|
|
||||||
q = from u in User,
|
q =
|
||||||
where: fragment("? @> ?", u.info, ^%{"ap_enabled" => true}),
|
from(
|
||||||
where: u.local == false
|
u in User,
|
||||||
|
where: fragment("? @> ?", u.info, ^%{"ap_enabled" => true}),
|
||||||
|
where: u.local == false
|
||||||
|
)
|
||||||
|
|
||||||
users = Repo.all(q)
|
users = Repo.all(q)
|
||||||
|
|
||||||
Enum.each(users, fn(user) ->
|
Enum.each(users, fn user ->
|
||||||
try do
|
try do
|
||||||
IO.puts("Fetching #{user.nickname}")
|
IO.puts("Fetching #{user.nickname}")
|
||||||
Pleroma.Web.ActivityPub.Transmogrifier.upgrade_user_from_ap_id(user.ap_id, false)
|
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
|
def run(_) do
|
||||||
IO.puts("Answer a few questions to generate a new config\n")
|
IO.puts("Answer a few questions to generate a new config\n")
|
||||||
IO.puts("--- THIS WILL OVERWRITE YOUR config/generated_config.exs! ---\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
|
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
|
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
|
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)
|
|
||||||
|
|
||||||
resultSql = EEx.eval_file("lib/mix/tasks/sample_psql.eex", [dbpass: dbpass])
|
mediaproxy =
|
||||||
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.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)
|
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)
|
File.write("config/setup_db.psql", resultSql)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,11 +9,20 @@ def run([nickname]) do
|
||||||
|
|
||||||
with %User{local: true} = user <- User.get_by_nickname(nickname),
|
with %User{local: true} = user <- User.get_by_nickname(nickname),
|
||||||
{:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
|
{:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
|
||||||
IO.puts "Generated password reset token for #{user.nickname}"
|
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(
|
||||||
|
"Url: #{
|
||||||
|
Pleroma.Web.Router.Helpers.util_url(
|
||||||
|
Pleroma.Web.Endpoint,
|
||||||
|
:show_password_reset,
|
||||||
|
token.token
|
||||||
|
)
|
||||||
|
}"
|
||||||
|
)
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
IO.puts "No local user #{nickname}"
|
IO.puts("No local user #{nickname}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,21 +7,24 @@ defmodule Mix.Tasks.SetModerator do
|
||||||
def run([nickname | rest]) do
|
def run([nickname | rest]) do
|
||||||
ensure_started(Repo, [])
|
ensure_started(Repo, [])
|
||||||
|
|
||||||
moderator = case rest do
|
moderator =
|
||||||
[moderator] -> moderator == "true"
|
case rest do
|
||||||
_ -> true
|
[moderator] -> moderator == "true"
|
||||||
end
|
_ -> true
|
||||||
|
end
|
||||||
|
|
||||||
with %User{local: true} = user <- User.get_by_nickname(nickname) do
|
with %User{local: true} = user <- User.get_by_nickname(nickname) do
|
||||||
info = user.info
|
info =
|
||||||
|> Map.put("is_moderator", !!moderator)
|
user.info
|
||||||
|
|> Map.put("is_moderator", !!moderator)
|
||||||
|
|
||||||
cng = User.info_changeset(user, %{info: info})
|
cng = User.info_changeset(user, %{info: info})
|
||||||
user = Repo.update!(cng)
|
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
|
else
|
||||||
_ ->
|
_ ->
|
||||||
IO.puts "No local user #{nickname}"
|
IO.puts("No local user #{nickname}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,15 +6,15 @@ defmodule Pleroma.PasswordResetToken do
|
||||||
alias Pleroma.{User, PasswordResetToken, Repo}
|
alias Pleroma.{User, PasswordResetToken, Repo}
|
||||||
|
|
||||||
schema "password_reset_tokens" do
|
schema "password_reset_tokens" do
|
||||||
belongs_to :user, User
|
belongs_to(:user, User)
|
||||||
field :token, :string
|
field(:token, :string)
|
||||||
field :used, :boolean, default: false
|
field(:used, :boolean, default: false)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_token(%User{} = user) do
|
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{
|
token = %PasswordResetToken{
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
|
|
|
@ -4,33 +4,53 @@ defmodule Pleroma.Activity do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
schema "activities" do
|
schema "activities" do
|
||||||
field :data, :map
|
field(:data, :map)
|
||||||
field :local, :boolean, default: true
|
field(:local, :boolean, default: true)
|
||||||
field :actor, :string
|
field(:actor, :string)
|
||||||
field :recipients, {:array, :string}
|
field(:recipients, {:array, :string})
|
||||||
has_many :notifications, Notification, on_delete: :delete_all
|
has_many(:notifications, Notification, on_delete: :delete_all)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_by_ap_id(ap_id) do
|
def get_by_ap_id(ap_id) do
|
||||||
Repo.one(from activity in Activity,
|
Repo.one(
|
||||||
where: fragment("(?)->>'id' = ?", activity.data, ^to_string(ap_id)))
|
from(
|
||||||
|
activity in Activity,
|
||||||
|
where: fragment("(?)->>'id' = ?", activity.data, ^to_string(ap_id))
|
||||||
|
)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
# Go through these and fix them everywhere.
|
# Go through these and fix them everywhere.
|
||||||
# Wrong name, only returns create activities
|
# Wrong name, only returns create activities
|
||||||
def all_by_object_ap_id_q(ap_id) do
|
def all_by_object_ap_id_q(ap_id) do
|
||||||
from activity in Activity,
|
from(
|
||||||
where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ?", activity.data, activity.data, ^to_string(ap_id)),
|
activity in Activity,
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
|
||||||
|
activity.data,
|
||||||
|
activity.data,
|
||||||
|
^to_string(ap_id)
|
||||||
|
),
|
||||||
where: fragment("(?)->>'type' = 'Create'", activity.data)
|
where: fragment("(?)->>'type' = 'Create'", activity.data)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Wrong name, returns all.
|
# Wrong name, returns all.
|
||||||
def all_non_create_by_object_ap_id_q(ap_id) do
|
def all_non_create_by_object_ap_id_q(ap_id) do
|
||||||
from activity in Activity,
|
from(
|
||||||
where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ?", activity.data, activity.data, ^to_string(ap_id))
|
activity in Activity,
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
|
||||||
|
activity.data,
|
||||||
|
activity.data,
|
||||||
|
^to_string(ap_id)
|
||||||
|
)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Wrong name plz fix thx
|
# Wrong name plz fix thx
|
||||||
|
@ -39,13 +59,21 @@ def all_by_object_ap_id(ap_id) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_activity_by_object_id_query(ap_ids) do
|
def create_activity_by_object_id_query(ap_ids) do
|
||||||
from activity in Activity,
|
from(
|
||||||
where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)", activity.data, activity.data, ^ap_ids),
|
activity in Activity,
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)",
|
||||||
|
activity.data,
|
||||||
|
activity.data,
|
||||||
|
^ap_ids
|
||||||
|
),
|
||||||
where: fragment("(?)->>'type' = 'Create'", activity.data)
|
where: fragment("(?)->>'type' = 'Create'", activity.data)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_create_activity_by_object_ap_id(ap_id) do
|
def get_create_activity_by_object_ap_id(ap_id) do
|
||||||
create_activity_by_object_id_query([ap_id])
|
create_activity_by_object_id_query([ap_id])
|
||||||
|> Repo.one
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,23 +7,34 @@ def start(_type, _args) do
|
||||||
import Supervisor.Spec
|
import Supervisor.Spec
|
||||||
|
|
||||||
# Define workers and child supervisors to be supervised
|
# Define workers and child supervisors to be supervised
|
||||||
children = [
|
children =
|
||||||
# Start the Ecto repository
|
[
|
||||||
supervisor(Pleroma.Repo, []),
|
# Start the Ecto repository
|
||||||
# Start the endpoint when the application starts
|
supervisor(Pleroma.Repo, []),
|
||||||
supervisor(Pleroma.Web.Endpoint, []),
|
# Start the endpoint when the application starts
|
||||||
# Start your own worker by calling: Pleroma.Worker.start_link(arg1, arg2, arg3)
|
supervisor(Pleroma.Web.Endpoint, []),
|
||||||
# worker(Pleroma.Worker, [arg1, arg2, arg3]),
|
# Start your own worker by calling: Pleroma.Worker.start_link(arg1, arg2, arg3)
|
||||||
worker(Cachex, [:user_cache, [
|
# worker(Pleroma.Worker, [arg1, arg2, arg3]),
|
||||||
default_ttl: 25000,
|
worker(Cachex, [
|
||||||
ttl_interval: 1000,
|
:user_cache,
|
||||||
limit: 2500
|
[
|
||||||
]]),
|
default_ttl: 25000,
|
||||||
worker(Pleroma.Web.Federator, []),
|
ttl_interval: 1000,
|
||||||
worker(Pleroma.Stats, []),
|
limit: 2500
|
||||||
]
|
]
|
||||||
++ if Mix.env == :test, do: [], else: [worker(Pleroma.Web.Streamer, [])]
|
]),
|
||||||
++ if !chat_enabled(), do: [], else: [worker(Pleroma.Web.ChatChannel.ChatChannelState, [])]
|
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
|
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
||||||
# for other strategies and supported options
|
# for other strategies and supported options
|
||||||
|
|
|
@ -5,19 +5,26 @@ defmodule Pleroma.Formatter do
|
||||||
@tag_regex ~r/\#\w+/u
|
@tag_regex ~r/\#\w+/u
|
||||||
def parse_tags(text, data \\ %{}) do
|
def parse_tags(text, data \\ %{}) do
|
||||||
Regex.scan(@tag_regex, text)
|
Regex.scan(@tag_regex, text)
|
||||||
|> Enum.map(fn (["#" <> tag = full_tag]) -> {full_tag, String.downcase(tag)} 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).()
|
|> (fn map ->
|
||||||
|
if data["sensitive"] in [true, "True", "true", "1"],
|
||||||
|
do: [{"#nsfw", "nsfw"}] ++ map,
|
||||||
|
else: map
|
||||||
|
end).()
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_mentions(text) do
|
def parse_mentions(text) do
|
||||||
# Modified from https://www.w3.org/TR/html5/forms.html#valid-e-mail-address
|
# 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)
|
Regex.scan(regex, text)
|
||||||
|> List.flatten
|
|> List.flatten()
|
||||||
|> Enum.uniq
|
|> Enum.uniq()
|
||||||
|> Enum.map(fn ("@" <> match = full_match) -> {full_match, User.get_cached_by_nickname(match)} end)
|
|> Enum.map(fn "@" <> match = full_match ->
|
||||||
|> Enum.filter(fn ({_match, user}) -> user end)
|
{full_match, User.get_cached_by_nickname(match)}
|
||||||
|
end)
|
||||||
|
|> Enum.filter(fn {_match, user} -> user end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@finmoji [
|
@finmoji [
|
||||||
|
@ -86,9 +93,9 @@ def parse_mentions(text) do
|
||||||
"woollysocks"
|
"woollysocks"
|
||||||
]
|
]
|
||||||
|
|
||||||
@finmoji_with_filenames Enum.map(@finmoji, fn (finmoji) ->
|
@finmoji_with_filenames Enum.map(@finmoji, fn finmoji ->
|
||||||
{finmoji, "/finmoji/128px/#{finmoji}-128.png"}
|
{finmoji, "/finmoji/128px/#{finmoji}-128.png"}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@emoji_from_file (with {:ok, default} <- File.read("config/emoji.txt") do
|
@emoji_from_file (with {:ok, default} <- File.read("config/emoji.txt") do
|
||||||
custom =
|
custom =
|
||||||
|
@ -97,31 +104,40 @@ def parse_mentions(text) do
|
||||||
else
|
else
|
||||||
_e -> ""
|
_e -> ""
|
||||||
end
|
end
|
||||||
|
|
||||||
(default <> "\n" <> custom)
|
(default <> "\n" <> custom)
|
||||||
|> String.trim()
|
|> String.trim()
|
||||||
|> String.split(~r/\n+/)
|
|> String.split(~r/\n+/)
|
||||||
|> Enum.map(fn(line) ->
|
|> Enum.map(fn line ->
|
||||||
[name, file] = String.split(line, ~r/,\s*/)
|
[name, file] = String.split(line, ~r/,\s*/)
|
||||||
{name, file}
|
{name, file}
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
_ -> []
|
_ -> []
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@emoji @finmoji_with_filenames ++ @emoji_from_file
|
@emoji @finmoji_with_filenames ++ @emoji_from_file
|
||||||
|
|
||||||
def emojify(text, emoji \\ @emoji)
|
def emojify(text, emoji \\ @emoji)
|
||||||
def emojify(text, nil), do: text
|
def emojify(text, nil), do: text
|
||||||
|
|
||||||
def emojify(text, emoji) do
|
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)
|
emoji = HtmlSanitizeEx.strip_tags(emoji)
|
||||||
file = HtmlSanitizeEx.strip_tags(file)
|
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)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_emoji(text) do
|
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
|
end
|
||||||
|
|
||||||
def get_custom_emoji() do
|
def get_custom_emoji() do
|
||||||
|
@ -141,59 +157,71 @@ def html_escape(text) do
|
||||||
|
|
||||||
@doc "changes http:... links to html links"
|
@doc "changes http:... links to html links"
|
||||||
def add_links({subs, text}) do
|
def add_links({subs, text}) do
|
||||||
links = Regex.scan(@link_regex, text)
|
links =
|
||||||
|> Enum.map(fn ([url]) -> {Ecto.UUID.generate, url} end)
|
Regex.scan(@link_regex, text)
|
||||||
|
|> Enum.map(fn [url] -> {Ecto.UUID.generate(), url} end)
|
||||||
|
|
||||||
uuid_text = links
|
uuid_text =
|
||||||
|> Enum.reduce(text, fn({uuid, url}, acc) -> String.replace(acc, url, uuid) end)
|
links
|
||||||
|
|> Enum.reduce(text, fn {uuid, url}, acc -> String.replace(acc, url, uuid) end)
|
||||||
|
|
||||||
subs = subs ++ Enum.map(links, fn({uuid, url}) ->
|
subs =
|
||||||
{uuid, "<a href='#{url}'>#{url}</a>"}
|
subs ++
|
||||||
end)
|
Enum.map(links, fn {uuid, url} ->
|
||||||
|
{uuid, "<a href='#{url}'>#{url}</a>"}
|
||||||
|
end)
|
||||||
|
|
||||||
{subs, uuid_text}
|
{subs, uuid_text}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Adds the links to mentioned users"
|
@doc "Adds the links to mentioned users"
|
||||||
def add_user_links({subs, text}, mentions) do
|
def add_user_links({subs, text}, mentions) do
|
||||||
mentions = mentions
|
mentions =
|
||||||
|> Enum.sort_by(fn ({name, _}) -> -String.length(name) end)
|
mentions
|
||||||
|> Enum.map(fn({name, user}) -> {name, user, Ecto.UUID.generate} end)
|
|> Enum.sort_by(fn {name, _} -> -String.length(name) end)
|
||||||
|
|> Enum.map(fn {name, user} -> {name, user, Ecto.UUID.generate()} end)
|
||||||
|
|
||||||
uuid_text = mentions
|
uuid_text =
|
||||||
|> Enum.reduce(text, fn ({match, _user, uuid}, text) ->
|
mentions
|
||||||
String.replace(text, match, uuid)
|
|> Enum.reduce(text, fn {match, _user, uuid}, text ->
|
||||||
end)
|
String.replace(text, match, uuid)
|
||||||
|
end)
|
||||||
|
|
||||||
subs = subs ++ Enum.map(mentions, fn ({match, %User{ap_id: ap_id}, uuid}) ->
|
subs =
|
||||||
short_match = String.split(match, "@") |> tl() |> hd()
|
subs ++
|
||||||
{uuid, "<span><a href='#{ap_id}'>@<span>#{short_match}</span></a></span>"}
|
Enum.map(mentions, fn {match, %User{ap_id: ap_id}, uuid} ->
|
||||||
end)
|
short_match = String.split(match, "@") |> tl() |> hd()
|
||||||
|
{uuid, "<span><a href='#{ap_id}'>@<span>#{short_match}</span></a></span>"}
|
||||||
|
end)
|
||||||
|
|
||||||
{subs, uuid_text}
|
{subs, uuid_text}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Adds the hashtag links"
|
@doc "Adds the hashtag links"
|
||||||
def add_hashtag_links({subs, text}, tags) do
|
def add_hashtag_links({subs, text}, tags) do
|
||||||
tags = tags
|
tags =
|
||||||
|> Enum.sort_by(fn ({name, _}) -> -String.length(name) end)
|
tags
|
||||||
|> Enum.map(fn({name, short}) -> {name, short, Ecto.UUID.generate} end)
|
|> Enum.sort_by(fn {name, _} -> -String.length(name) end)
|
||||||
|
|> Enum.map(fn {name, short} -> {name, short, Ecto.UUID.generate()} end)
|
||||||
|
|
||||||
uuid_text = tags
|
uuid_text =
|
||||||
|> Enum.reduce(text, fn ({match, _short, uuid}, text) ->
|
tags
|
||||||
String.replace(text, match, uuid)
|
|> Enum.reduce(text, fn {match, _short, uuid}, text ->
|
||||||
end)
|
String.replace(text, match, uuid)
|
||||||
|
end)
|
||||||
|
|
||||||
subs = subs ++ Enum.map(tags, fn ({_, tag, uuid}) ->
|
subs =
|
||||||
url = "<a href='#{Pleroma.Web.base_url}/tag/#{tag}' rel='tag'>##{tag}</a>"
|
subs ++
|
||||||
{uuid, url}
|
Enum.map(tags, fn {_, tag, uuid} ->
|
||||||
end)
|
url = "<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>##{tag}</a>"
|
||||||
|
{uuid, url}
|
||||||
|
end)
|
||||||
|
|
||||||
{subs, uuid_text}
|
{subs, uuid_text}
|
||||||
end
|
end
|
||||||
|
|
||||||
def finalize({subs, text}) do
|
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)
|
String.replace(result_text, uuid, replacement)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
|
|
||||||
defmodule Pleroma.HTTP do
|
defmodule Pleroma.HTTP do
|
||||||
use HTTPoison.Base
|
use HTTPoison.Base
|
||||||
|
|
||||||
def process_request_options(options) do
|
def process_request_options(options) do
|
||||||
config = Application.get_env(:pleroma, :http, [])
|
config = Application.get_env(:pleroma, :http, [])
|
||||||
proxy = Keyword.get(config, :proxy_url, nil)
|
proxy = Keyword.get(config, :proxy_url, nil)
|
||||||
|
|
||||||
case proxy do
|
case proxy do
|
||||||
nil -> options
|
nil -> options
|
||||||
_ -> options ++ [proxy: proxy]
|
_ -> options ++ [proxy: proxy]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,75 +4,89 @@ defmodule Pleroma.Notification do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
schema "notifications" do
|
schema "notifications" do
|
||||||
field :seen, :boolean, default: false
|
field(:seen, :boolean, default: false)
|
||||||
belongs_to :user, Pleroma.User
|
belongs_to(:user, Pleroma.User)
|
||||||
belongs_to :activity, Pleroma.Activity
|
belongs_to(:activity, Pleroma.Activity)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Make generic and unify (see activity_pub.ex)
|
# TODO: Make generic and unify (see activity_pub.ex)
|
||||||
defp restrict_max(query, %{"max_id" => max_id}) do
|
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
|
end
|
||||||
|
|
||||||
defp restrict_max(query, _), do: query
|
defp restrict_max(query, _), do: query
|
||||||
|
|
||||||
defp restrict_since(query, %{"since_id" => since_id}) do
|
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
|
end
|
||||||
|
|
||||||
defp restrict_since(query, _), do: query
|
defp restrict_since(query, _), do: query
|
||||||
|
|
||||||
def for_user(user, opts \\ %{}) do
|
def for_user(user, opts \\ %{}) do
|
||||||
query = from n in Notification,
|
query =
|
||||||
where: n.user_id == ^user.id,
|
from(
|
||||||
order_by: [desc: n.id],
|
n in Notification,
|
||||||
preload: [:activity],
|
where: n.user_id == ^user.id,
|
||||||
limit: 20
|
order_by: [desc: n.id],
|
||||||
|
preload: [:activity],
|
||||||
|
limit: 20
|
||||||
|
)
|
||||||
|
|
||||||
query = query
|
query =
|
||||||
|> restrict_since(opts)
|
query
|
||||||
|> restrict_max(opts)
|
|> restrict_since(opts)
|
||||||
|
|> restrict_max(opts)
|
||||||
|
|
||||||
Repo.all(query)
|
Repo.all(query)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get(%{id: user_id} = _user, id) do
|
def get(%{id: user_id} = _user, id) do
|
||||||
query = from n in Notification,
|
query =
|
||||||
where: n.id == ^id,
|
from(
|
||||||
preload: [:activity]
|
n in Notification,
|
||||||
|
where: n.id == ^id,
|
||||||
|
preload: [:activity]
|
||||||
|
)
|
||||||
|
|
||||||
notification = Repo.one(query)
|
notification = Repo.one(query)
|
||||||
|
|
||||||
case notification do
|
case notification do
|
||||||
%{user_id: ^user_id} ->
|
%{user_id: ^user_id} ->
|
||||||
{:ok, notification}
|
{:ok, notification}
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
{:error, "Cannot get notification"}
|
{:error, "Cannot get notification"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear(user) do
|
def clear(user) do
|
||||||
query = from n in Notification,
|
query = from(n in Notification, where: n.user_id == ^user.id)
|
||||||
where: n.user_id == ^user.id
|
|
||||||
|
|
||||||
Repo.delete_all(query)
|
Repo.delete_all(query)
|
||||||
end
|
end
|
||||||
|
|
||||||
def dismiss(%{id: user_id} = _user, id) do
|
def dismiss(%{id: user_id} = _user, id) do
|
||||||
notification = Repo.get(Notification, id)
|
notification = Repo.get(Notification, id)
|
||||||
|
|
||||||
case notification do
|
case notification do
|
||||||
%{user_id: ^user_id} ->
|
%{user_id: ^user_id} ->
|
||||||
Repo.delete(notification)
|
Repo.delete(notification)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
{:error, "Cannot dismiss notification"}
|
{:error, "Cannot dismiss notification"}
|
||||||
end
|
end
|
||||||
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)
|
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}
|
{:ok, notifications}
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_notifications(_), do: {:ok, []}
|
def create_notifications(_), do: {:ok, []}
|
||||||
|
|
||||||
# TODO move to sql, too.
|
# TODO move to sql, too.
|
||||||
|
@ -85,4 +99,3 @@ def create_notification(%Activity{} = activity, %User{} = user) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,14 +4,14 @@ defmodule Pleroma.Object do
|
||||||
import Ecto.{Query, Changeset}
|
import Ecto.{Query, Changeset}
|
||||||
|
|
||||||
schema "objects" do
|
schema "objects" do
|
||||||
field :data, :map
|
field(:data, :map)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
def create(data) do
|
def create(data) do
|
||||||
Object.change(%Object{}, %{data: data})
|
Object.change(%Object{}, %{data: data})
|
||||||
|> Repo.insert
|
|> Repo.insert()
|
||||||
end
|
end
|
||||||
|
|
||||||
def change(struct, params \\ %{}) do
|
def change(struct, params \\ %{}) do
|
||||||
|
@ -22,24 +22,30 @@ def change(struct, params \\ %{}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_by_ap_id(nil), do: nil
|
def get_by_ap_id(nil), do: nil
|
||||||
|
|
||||||
def get_by_ap_id(ap_id) do
|
def get_by_ap_id(ap_id) do
|
||||||
Repo.one(from object in Object,
|
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
|
||||||
where: fragment("(?)->>'id' = ?", object.data, ^ap_id))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_cached_by_ap_id(ap_id) do
|
def get_cached_by_ap_id(ap_id) do
|
||||||
if Mix.env == :test do
|
if Mix.env() == :test do
|
||||||
get_by_ap_id(ap_id)
|
get_by_ap_id(ap_id)
|
||||||
else
|
else
|
||||||
key = "object:#{ap_id}"
|
key = "object:#{ap_id}"
|
||||||
Cachex.get!(:user_cache, key, fallback: fn(_) ->
|
|
||||||
object = get_by_ap_id(ap_id)
|
Cachex.get!(
|
||||||
if object do
|
:user_cache,
|
||||||
{:commit, object}
|
key,
|
||||||
else
|
fallback: fn _ ->
|
||||||
{:ignore, object}
|
object = get_by_ap_id(ap_id)
|
||||||
|
|
||||||
|
if object do
|
||||||
|
{:commit, object}
|
||||||
|
else
|
||||||
|
{:ignore, object}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,7 @@ def call(conn, opts) do
|
||||||
{:ok, user} <- opts[:fetcher].(username),
|
{:ok, user} <- opts[:fetcher].(username),
|
||||||
false <- !!user.info["deactivated"],
|
false <- !!user.info["deactivated"],
|
||||||
saved_user_id <- get_session(conn, :user_id),
|
saved_user_id <- get_session(conn, :user_id),
|
||||||
{:ok, verified_user} <- verify(user, password, saved_user_id)
|
{:ok, verified_user} <- verify(user, password, saved_user_id) do
|
||||||
do
|
|
||||||
conn
|
conn
|
||||||
|> assign(:user, verified_user)
|
|> assign(:user, verified_user)
|
||||||
|> put_session(:user_id, verified_user.id)
|
|> put_session(:user_id, verified_user.id)
|
||||||
|
@ -30,7 +29,7 @@ defp verify(%{id: id} = user, _password, id) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp verify(nil, _password, _user_id) do
|
defp verify(nil, _password, _user_id) do
|
||||||
Pbkdf2.dummy_checkpw
|
Pbkdf2.dummy_checkpw()
|
||||||
:error
|
:error
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -45,8 +44,7 @@ defp verify(user, password, _user_id) do
|
||||||
defp decode_header(conn) do
|
defp decode_header(conn) do
|
||||||
with ["Basic " <> header] <- get_req_header(conn, "authorization"),
|
with ["Basic " <> header] <- get_req_header(conn, "authorization"),
|
||||||
{:ok, userinfo} <- Base.decode64(header),
|
{:ok, userinfo} <- Base.decode64(header),
|
||||||
[username, password] <- String.split(userinfo, ":", parts: 2)
|
[username, password] <- String.split(userinfo, ":", parts: 2) do
|
||||||
do
|
|
||||||
{:ok, username, password}
|
{:ok, username, password}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,11 +9,14 @@ def init(options) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
|
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
|
||||||
|
|
||||||
def call(conn, _) do
|
def call(conn, _) do
|
||||||
token = case get_req_header(conn, "authorization") do
|
token =
|
||||||
["Bearer " <> header] -> header
|
case get_req_header(conn, "authorization") do
|
||||||
_ -> get_session(conn, :oauth_token)
|
["Bearer " <> header] -> header
|
||||||
end
|
_ -> get_session(conn, :oauth_token)
|
||||||
|
end
|
||||||
|
|
||||||
with token when not is_nil(token) <- token,
|
with token when not is_nil(token) <- token,
|
||||||
%Token{user_id: user_id} <- Repo.get_by(Token, token: token),
|
%Token{user_id: user_id} <- Repo.get_by(Token, token: token),
|
||||||
%User{} = user <- Repo.get(User, user_id),
|
%User{} = user <- Repo.get(User, user_id),
|
||||||
|
|
|
@ -18,22 +18,31 @@ def get_peers do
|
||||||
|
|
||||||
def schedule_update do
|
def schedule_update do
|
||||||
spawn(fn ->
|
spawn(fn ->
|
||||||
Process.sleep(1000 * 60 * 60 * 1) # 1 hour
|
# 1 hour
|
||||||
|
Process.sleep(1000 * 60 * 60 * 1)
|
||||||
schedule_update()
|
schedule_update()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
update_stats()
|
update_stats()
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_stats do
|
def update_stats do
|
||||||
peers = from(u in Pleroma.User,
|
peers =
|
||||||
select: fragment("distinct ?->'host'", u.info),
|
from(
|
||||||
where: u.local != ^true)
|
u in Pleroma.User,
|
||||||
|> Repo.all()
|
select: fragment("distinct ?->'host'", u.info),
|
||||||
|
where: u.local != ^true
|
||||||
|
)
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
domain_count = Enum.count(peers)
|
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)
|
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 _ ->
|
Agent.update(__MODULE__, fn _ ->
|
||||||
{peers, %{domain_count: domain_count, status_count: status_count, user_count: user_count}}
|
{peers, %{domain_count: domain_count, status_count: status_count, user_count: user_count}}
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -1,27 +1,31 @@
|
||||||
defmodule Pleroma.Upload do
|
defmodule Pleroma.Upload do
|
||||||
alias Ecto.UUID
|
alias Ecto.UUID
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
|
|
||||||
def store(%Plug.Upload{} = file) do
|
def store(%Plug.Upload{} = file) do
|
||||||
uuid = UUID.generate
|
uuid = UUID.generate()
|
||||||
upload_folder = Path.join(upload_path(), uuid)
|
upload_folder = Path.join(upload_path(), uuid)
|
||||||
File.mkdir_p!(upload_folder)
|
File.mkdir_p!(upload_folder)
|
||||||
result_file = Path.join(upload_folder, file.filename)
|
result_file = Path.join(upload_folder, file.filename)
|
||||||
File.cp!(file.path, result_file)
|
File.cp!(file.path, result_file)
|
||||||
|
|
||||||
# fix content type on some image uploads
|
# fix content type on some image uploads
|
||||||
content_type = if file.content_type in [nil, "application/octet-stream"] do
|
content_type =
|
||||||
get_content_type(file.path)
|
if file.content_type in [nil, "application/octet-stream"] do
|
||||||
else
|
get_content_type(file.path)
|
||||||
file.content_type
|
else
|
||||||
end
|
file.content_type
|
||||||
|
end
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"type" => "Image",
|
"type" => "Image",
|
||||||
"url" => [%{
|
"url" => [
|
||||||
"type" => "Link",
|
%{
|
||||||
"mediaType" => content_type,
|
"type" => "Link",
|
||||||
"href" => url_for(Path.join(uuid, :cow_uri.urlencode(file.filename)))
|
"mediaType" => content_type,
|
||||||
}],
|
"href" => url_for(Path.join(uuid, :cow_uri.urlencode(file.filename)))
|
||||||
|
}
|
||||||
|
],
|
||||||
"name" => file.filename,
|
"name" => file.filename,
|
||||||
"uuid" => uuid
|
"uuid" => uuid
|
||||||
}
|
}
|
||||||
|
@ -30,7 +34,7 @@ def store(%Plug.Upload{} = file) do
|
||||||
def store(%{"img" => "data:image/" <> image_data}) do
|
def store(%{"img" => "data:image/" <> image_data}) do
|
||||||
parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data)
|
parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data)
|
||||||
data = Base.decode64!(parsed["data"])
|
data = Base.decode64!(parsed["data"])
|
||||||
uuid = UUID.generate
|
uuid = UUID.generate()
|
||||||
upload_folder = Path.join(upload_path(), uuid)
|
upload_folder = Path.join(upload_path(), uuid)
|
||||||
File.mkdir_p!(upload_folder)
|
File.mkdir_p!(upload_folder)
|
||||||
filename = Base.encode16(:crypto.hash(:sha256, data)) <> ".#{parsed["filetype"]}"
|
filename = Base.encode16(:crypto.hash(:sha256, data)) <> ".#{parsed["filetype"]}"
|
||||||
|
@ -42,11 +46,13 @@ def store(%{"img" => "data:image/" <> image_data}) do
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"type" => "Image",
|
"type" => "Image",
|
||||||
"url" => [%{
|
"url" => [
|
||||||
"type" => "Link",
|
%{
|
||||||
"mediaType" => content_type,
|
"type" => "Link",
|
||||||
"href" => url_for(Path.join(uuid, :cow_uri.urlencode(filename)))
|
"mediaType" => content_type,
|
||||||
}],
|
"href" => url_for(Path.join(uuid, :cow_uri.urlencode(filename)))
|
||||||
|
}
|
||||||
|
],
|
||||||
"name" => filename,
|
"name" => filename,
|
||||||
"uuid" => uuid
|
"uuid" => uuid
|
||||||
}
|
}
|
||||||
|
@ -62,28 +68,37 @@ defp url_for(file) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_content_type(file) do
|
def get_content_type(file) do
|
||||||
match = File.open(file, [:read], fn(f) ->
|
match =
|
||||||
case IO.binread(f, 8) do
|
File.open(file, [:read], fn f ->
|
||||||
<<0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a>> ->
|
case IO.binread(f, 8) do
|
||||||
"image/png"
|
<<0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A>> ->
|
||||||
<<0x47, 0x49, 0x46, 0x38, _, 0x61, _, _>> ->
|
"image/png"
|
||||||
"image/gif"
|
|
||||||
<<0xff, 0xd8, 0xff, _, _, _, _, _>> ->
|
<<0x47, 0x49, 0x46, 0x38, _, 0x61, _, _>> ->
|
||||||
"image/jpeg"
|
"image/gif"
|
||||||
<<0x1a, 0x45, 0xdf, 0xa3, _, _, _, _>> ->
|
|
||||||
"video/webm"
|
<<0xFF, 0xD8, 0xFF, _, _, _, _, _>> ->
|
||||||
<<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70>> ->
|
"image/jpeg"
|
||||||
"video/mp4"
|
|
||||||
<<0x49, 0x44, 0x33, _, _, _, _, _>> ->
|
<<0x1A, 0x45, 0xDF, 0xA3, _, _, _, _>> ->
|
||||||
"audio/mpeg"
|
"video/webm"
|
||||||
<<0x4f, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00>> ->
|
|
||||||
"audio/ogg"
|
<<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70>> ->
|
||||||
<<0x52, 0x49, 0x46, 0x46, _, _, _, _>> ->
|
"video/mp4"
|
||||||
"audio/wav"
|
|
||||||
_ ->
|
<<0x49, 0x44, 0x33, _, _, _, _, _>> ->
|
||||||
"application/octet-stream"
|
"audio/mpeg"
|
||||||
end
|
|
||||||
end)
|
<<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00>> ->
|
||||||
|
"audio/ogg"
|
||||||
|
|
||||||
|
<<0x52, 0x49, 0x46, 0x46, _, _, _, _>> ->
|
||||||
|
"audio/wav"
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
"application/octet-stream"
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
case match do
|
case match do
|
||||||
{:ok, type} -> type
|
{:ok, type} -> type
|
||||||
|
|
|
@ -8,20 +8,20 @@ defmodule Pleroma.User do
|
||||||
alias Pleroma.Web.ActivityPub.{Utils, ActivityPub}
|
alias Pleroma.Web.ActivityPub.{Utils, ActivityPub}
|
||||||
|
|
||||||
schema "users" do
|
schema "users" do
|
||||||
field :bio, :string
|
field(:bio, :string)
|
||||||
field :email, :string
|
field(:email, :string)
|
||||||
field :name, :string
|
field(:name, :string)
|
||||||
field :nickname, :string
|
field(:nickname, :string)
|
||||||
field :password_hash, :string
|
field(:password_hash, :string)
|
||||||
field :password, :string, virtual: true
|
field(:password, :string, virtual: true)
|
||||||
field :password_confirmation, :string, virtual: true
|
field(:password_confirmation, :string, virtual: true)
|
||||||
field :following, {:array, :string}, default: []
|
field(:following, {:array, :string}, default: [])
|
||||||
field :ap_id, :string
|
field(:ap_id, :string)
|
||||||
field :avatar, :map
|
field(:avatar, :map)
|
||||||
field :local, :boolean, default: true
|
field(:local, :boolean, default: true)
|
||||||
field :info, :map, default: %{}
|
field(:info, :map, default: %{})
|
||||||
field :follower_address, :string
|
field(:follower_address, :string)
|
||||||
has_many :notifications, Notification
|
has_many(:notifications, Notification)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
@ -41,7 +41,7 @@ def banner_url(user) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def ap_id(%User{nickname: nickname}) do
|
def ap_id(%User{nickname: nickname}) do
|
||||||
"#{Web.base_url}/users/#{nickname}"
|
"#{Web.base_url()}/users/#{nickname}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def ap_followers(%User{} = user) do
|
def ap_followers(%User{} = user) do
|
||||||
|
@ -62,6 +62,7 @@ def info_changeset(struct, params \\ %{}) do
|
||||||
|
|
||||||
def user_info(%User{} = user) do
|
def user_info(%User{} = user) do
|
||||||
oneself = if user.local, do: 1, else: 0
|
oneself = if user.local, do: 1, else: 0
|
||||||
|
|
||||||
%{
|
%{
|
||||||
following_count: length(user.following) - oneself,
|
following_count: length(user.following) - oneself,
|
||||||
note_count: user.info["note_count"] || 0,
|
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])?)*$/
|
@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
|
def remote_user_creation(params) do
|
||||||
changes = %User{}
|
changes =
|
||||||
|> cast(params, [:bio, :name, :ap_id, :nickname, :info, :avatar])
|
%User{}
|
||||||
|> validate_required([:name, :ap_id, :nickname])
|
|> cast(params, [:bio, :name, :ap_id, :nickname, :info, :avatar])
|
||||||
|> unique_constraint(:nickname)
|
|> validate_required([:name, :ap_id, :nickname])
|
||||||
|> validate_format(:nickname, @email_regex)
|
|> unique_constraint(:nickname)
|
||||||
|> validate_length(:bio, max: 5000)
|
|> validate_format(:nickname, @email_regex)
|
||||||
|> validate_length(:name, max: 100)
|
|> validate_length(:bio, max: 5000)
|
||||||
|> put_change(:local, false)
|
|> validate_length(:name, max: 100)
|
||||||
|
|> put_change(:local, false)
|
||||||
|
|
||||||
if changes.valid? do
|
if changes.valid? do
|
||||||
case changes.changes[:info]["source_data"] do
|
case changes.changes[:info]["source_data"] do
|
||||||
%{"followers" => followers} ->
|
%{"followers" => followers} ->
|
||||||
changes
|
changes
|
||||||
|> put_change(:follower_address, followers)
|
|> put_change(:follower_address, followers)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
|
followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
|
||||||
|
|
||||||
changes
|
changes
|
||||||
|> put_change(:follower_address, followers)
|
|> put_change(:follower_address, followers)
|
||||||
end
|
end
|
||||||
|
@ -113,13 +118,15 @@ def upgrade_changeset(struct, params \\ %{}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def password_update_changeset(struct, params) do
|
def password_update_changeset(struct, params) do
|
||||||
changeset = struct
|
changeset =
|
||||||
|> cast(params, [:password, :password_confirmation])
|
struct
|
||||||
|> validate_required([:password, :password_confirmation])
|
|> cast(params, [:password, :password_confirmation])
|
||||||
|> validate_confirmation(:password)
|
|> validate_required([:password, :password_confirmation])
|
||||||
|
|> validate_confirmation(:password)
|
||||||
|
|
||||||
if changeset.valid? do
|
if changeset.valid? do
|
||||||
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
||||||
|
|
||||||
changeset
|
changeset
|
||||||
|> put_change(:password_hash, hashed)
|
|> put_change(:password_hash, hashed)
|
||||||
else
|
else
|
||||||
|
@ -132,21 +139,23 @@ def reset_password(user, data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_changeset(struct, params \\ %{}) do
|
def register_changeset(struct, params \\ %{}) do
|
||||||
changeset = struct
|
changeset =
|
||||||
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|
struct
|
||||||
|> validate_required([:email, :name, :nickname, :password, :password_confirmation])
|
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|
||||||
|> validate_confirmation(:password)
|
|> validate_required([:email, :name, :nickname, :password, :password_confirmation])
|
||||||
|> unique_constraint(:email)
|
|> validate_confirmation(:password)
|
||||||
|> unique_constraint(:nickname)
|
|> unique_constraint(:email)
|
||||||
|> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/)
|
|> unique_constraint(:nickname)
|
||||||
|> validate_format(:email, @email_regex)
|
|> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/)
|
||||||
|> validate_length(:bio, max: 1000)
|
|> validate_format(:email, @email_regex)
|
||||||
|> validate_length(:name, min: 1, max: 100)
|
|> validate_length(:bio, max: 1000)
|
||||||
|
|> validate_length(:name, min: 1, max: 100)
|
||||||
|
|
||||||
if changeset.valid? do
|
if changeset.valid? do
|
||||||
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
||||||
ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
|
ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
|
||||||
followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]})
|
followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]})
|
||||||
|
|
||||||
changeset
|
changeset
|
||||||
|> put_change(:password_hash, hashed)
|
|> put_change(:password_hash, hashed)
|
||||||
|> put_change(:ap_id, ap_id)
|
|> put_change(:ap_id, ap_id)
|
||||||
|
@ -161,19 +170,20 @@ def follow(%User{} = follower, %User{info: info} = followed) do
|
||||||
ap_followers = followed.follower_address
|
ap_followers = followed.follower_address
|
||||||
|
|
||||||
if following?(follower, followed) or info["deactivated"] do
|
if following?(follower, followed) or info["deactivated"] do
|
||||||
{:error,
|
{:error, "Could not follow user: #{followed.nickname} is already on your list."}
|
||||||
"Could not follow user: #{followed.nickname} is already on your list."}
|
|
||||||
else
|
else
|
||||||
if !followed.local && follower.local && !ap_enabled?(followed) do
|
if !followed.local && follower.local && !ap_enabled?(followed) do
|
||||||
Websub.subscribe(follower, followed)
|
Websub.subscribe(follower, followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
following = [ap_followers | follower.following]
|
following =
|
||||||
|> Enum.uniq
|
[ap_followers | follower.following]
|
||||||
|
|> Enum.uniq()
|
||||||
|
|
||||||
follower = follower
|
follower =
|
||||||
|> follow_changeset(%{following: following})
|
follower
|
||||||
|> update_and_set_cache
|
|> follow_changeset(%{following: following})
|
||||||
|
|> update_and_set_cache
|
||||||
|
|
||||||
{:ok, _} = update_follower_count(followed)
|
{: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
|
def unfollow(%User{} = follower, %User{} = followed) do
|
||||||
ap_followers = followed.follower_address
|
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
|
if following?(follower, followed) and follower.ap_id != followed.ap_id do
|
||||||
|> follow_changeset(%{following: following})
|
following =
|
||||||
|> update_and_set_cache
|
follower.following
|
||||||
|
|> List.delete(ap_followers)
|
||||||
|
|
||||||
|
{:ok, follower} =
|
||||||
|
follower
|
||||||
|
|> follow_changeset(%{following: following})
|
||||||
|
|> update_and_set_cache
|
||||||
|
|
||||||
{:ok, followed} = update_follower_count(followed)
|
{:ok, followed} = update_follower_count(followed)
|
||||||
|
|
||||||
|
@ -225,12 +238,12 @@ def invalidate_cache(user) do
|
||||||
|
|
||||||
def get_cached_by_ap_id(ap_id) do
|
def get_cached_by_ap_id(ap_id) do
|
||||||
key = "ap_id:#{ap_id}"
|
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
|
end
|
||||||
|
|
||||||
def get_cached_by_nickname(nickname) do
|
def get_cached_by_nickname(nickname) do
|
||||||
key = "nickname:#{nickname}"
|
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
|
end
|
||||||
|
|
||||||
def get_by_nickname(nickname) do
|
def get_by_nickname(nickname) do
|
||||||
|
@ -239,7 +252,7 @@ def get_by_nickname(nickname) do
|
||||||
|
|
||||||
def get_cached_user_info(user) do
|
def get_cached_user_info(user) do
|
||||||
key = "user_info:#{user.id}"
|
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
|
end
|
||||||
|
|
||||||
def fetch_by_nickname(nickname) do
|
def fetch_by_nickname(nickname) do
|
||||||
|
@ -252,29 +265,37 @@ def fetch_by_nickname(nickname) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_or_fetch_by_nickname(nickname) do
|
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
|
user
|
||||||
else _e ->
|
else
|
||||||
with [_nick, _domain] <- String.split(nickname, "@"),
|
_e ->
|
||||||
{:ok, user} <- fetch_by_nickname(nickname) do
|
with [_nick, _domain] <- String.split(nickname, "@"),
|
||||||
user
|
{:ok, user} <- fetch_by_nickname(nickname) do
|
||||||
else _e -> nil
|
user
|
||||||
end
|
else
|
||||||
|
_e -> nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_followers(%User{id: id, follower_address: follower_address}) do
|
def get_followers(%User{id: id, follower_address: follower_address}) do
|
||||||
q = from u in User,
|
q =
|
||||||
where: fragment("? <@ ?", ^[follower_address], u.following),
|
from(
|
||||||
where: u.id != ^id
|
u in User,
|
||||||
|
where: fragment("? <@ ?", ^[follower_address], u.following),
|
||||||
|
where: u.id != ^id
|
||||||
|
)
|
||||||
|
|
||||||
{:ok, Repo.all(q)}
|
{:ok, Repo.all(q)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_friends(%User{id: id, following: following}) do
|
def get_friends(%User{id: id, following: following}) do
|
||||||
q = from u in User,
|
q =
|
||||||
where: u.follower_address in ^following,
|
from(
|
||||||
where: u.id != ^id
|
u in User,
|
||||||
|
where: u.follower_address in ^following,
|
||||||
|
where: u.id != ^id
|
||||||
|
)
|
||||||
|
|
||||||
{:ok, Repo.all(q)}
|
{:ok, Repo.all(q)}
|
||||||
end
|
end
|
||||||
|
@ -289,9 +310,12 @@ def increase_note_count(%User{} = user) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_note_count(%User{} = user) do
|
def update_note_count(%User{} = user) do
|
||||||
note_count_query = from a in Object,
|
note_count_query =
|
||||||
where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
|
from(
|
||||||
select: count(a.id)
|
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)
|
note_count = Repo.one(note_count_query)
|
||||||
|
|
||||||
|
@ -303,10 +327,13 @@ def update_note_count(%User{} = user) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_follower_count(%User{} = user) do
|
def update_follower_count(%User{} = user) do
|
||||||
follower_count_query = from u in User,
|
follower_count_query =
|
||||||
where: ^user.follower_address in u.following,
|
from(
|
||||||
where: u.id != ^user.id,
|
u in User,
|
||||||
select: count(u.id)
|
where: ^user.follower_address in u.following,
|
||||||
|
where: u.id != ^user.id,
|
||||||
|
select: count(u.id)
|
||||||
|
)
|
||||||
|
|
||||||
follower_count = Repo.one(follower_count_query)
|
follower_count = Repo.one(follower_count_query)
|
||||||
|
|
||||||
|
@ -318,20 +345,25 @@ def update_follower_count(%User{} = user) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_notified_from_activity(%Activity{recipients: to}) do
|
def get_notified_from_activity(%Activity{recipients: to}) do
|
||||||
query = from u in User,
|
query =
|
||||||
where: u.ap_id in ^to,
|
from(
|
||||||
where: u.local == true
|
u in User,
|
||||||
|
where: u.ap_id in ^to,
|
||||||
|
where: u.local == true
|
||||||
|
)
|
||||||
|
|
||||||
Repo.all(query)
|
Repo.all(query)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_recipients_from_activity(%Activity{recipients: to}) do
|
def get_recipients_from_activity(%Activity{recipients: to}) do
|
||||||
query = from u in User,
|
query =
|
||||||
where: u.ap_id in ^to,
|
from(
|
||||||
or_where: fragment("? && ?", u.following, ^to)
|
u in User,
|
||||||
|
where: u.ap_id in ^to,
|
||||||
|
or_where: fragment("? && ?", u.following, ^to)
|
||||||
|
)
|
||||||
|
|
||||||
query = from u in query,
|
query = from(u in query, where: u.local == true)
|
||||||
where: u.local == true
|
|
||||||
|
|
||||||
Repo.all(query)
|
Repo.all(query)
|
||||||
end
|
end
|
||||||
|
@ -340,9 +372,20 @@ def search(query, resolve) do
|
||||||
if resolve do
|
if resolve do
|
||||||
User.get_or_fetch_by_nickname(query)
|
User.get_or_fetch_by_nickname(query)
|
||||||
end
|
end
|
||||||
q = from u in User,
|
|
||||||
where: fragment("(to_tsvector('english', ?) || to_tsvector('english', ?)) @@ plainto_tsquery('english', ?)", u.nickname, u.name, ^query),
|
q =
|
||||||
limit: 20
|
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)
|
Repo.all(q)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -370,36 +413,40 @@ def blocks?(user, %{ap_id: ap_id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def local_user_query() do
|
def local_user_query() do
|
||||||
from u in User,
|
from(u in User, where: u.local == true)
|
||||||
where: u.local == true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def deactivate (%User{} = user) do
|
def deactivate(%User{} = user) do
|
||||||
new_info = Map.put(user.info, "deactivated", true)
|
new_info = Map.put(user.info, "deactivated", true)
|
||||||
cs = User.info_changeset(user, %{info: new_info})
|
cs = User.info_changeset(user, %{info: new_info})
|
||||||
update_and_set_cache(cs)
|
update_and_set_cache(cs)
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete (%User{} = user) do
|
def delete(%User{} = user) do
|
||||||
{:ok, user} = User.deactivate(user)
|
{:ok, user} = User.deactivate(user)
|
||||||
|
|
||||||
# Remove all relationships
|
# Remove all relationships
|
||||||
{:ok, followers } = User.get_followers(user)
|
{:ok, followers} = User.get_followers(user)
|
||||||
|
|
||||||
followers
|
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)
|
{:ok, friends} = User.get_friends(user)
|
||||||
friends
|
|
||||||
|> Enum.each(fn (followed) -> User.unfollow(user, followed) end)
|
|
||||||
|
|
||||||
query = from a in Activity,
|
friends
|
||||||
where: a.actor == ^user.ap_id
|
|> Enum.each(fn followed -> User.unfollow(user, followed) end)
|
||||||
|
|
||||||
|
query = from(a in Activity, where: a.actor == ^user.ap_id)
|
||||||
|
|
||||||
Repo.all(query)
|
Repo.all(query)
|
||||||
|> Enum.each(fn (activity) ->
|
|> Enum.each(fn activity ->
|
||||||
case activity.data["type"] do
|
case activity.data["type"] do
|
||||||
"Create" -> ActivityPub.delete(Object.get_by_ap_id(activity.data["object"]["id"]))
|
"Create" ->
|
||||||
_ -> "Doing nothing" # TODO: Do something with likes, follows, repeats.
|
ActivityPub.delete(Object.get_by_ap_id(activity.data["object"]["id"]))
|
||||||
|
|
||||||
|
# TODO: Do something with likes, follows, repeats.
|
||||||
|
_ ->
|
||||||
|
"Doing nothing"
|
||||||
end
|
end
|
||||||
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)
|
ap_try = ActivityPub.make_user_from_ap_id(ap_id)
|
||||||
|
|
||||||
case ap_try do
|
case ap_try do
|
||||||
{:ok, user} -> user
|
{:ok, user} ->
|
||||||
|
user
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
case OStatus.make_user(ap_id) do
|
case OStatus.make_user(ap_id) do
|
||||||
{:ok, user} -> user
|
{:ok, user} -> user
|
||||||
|
@ -424,12 +473,15 @@ def get_or_fetch_by_ap_id(ap_id) do
|
||||||
end
|
end
|
||||||
|
|
||||||
# AP style
|
# AP style
|
||||||
def public_key_from_info(%{"source_data" => %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
|
def public_key_from_info(%{
|
||||||
key = :public_key.pem_decode(public_key_pem)
|
"source_data" => %{"publicKey" => %{"publicKeyPem" => public_key_pem}}
|
||||||
|> hd()
|
}) do
|
||||||
|> :public_key.pem_entry_decode()
|
key =
|
||||||
|
:public_key.pem_decode(public_key_pem)
|
||||||
|
|> hd()
|
||||||
|
|> :public_key.pem_entry_decode()
|
||||||
|
|
||||||
{:ok, key}
|
{:ok, key}
|
||||||
end
|
end
|
||||||
|
|
||||||
# OStatus Magic Key
|
# OStatus Magic Key
|
||||||
|
@ -450,8 +502,10 @@ defp blank?(""), do: nil
|
||||||
defp blank?(n), do: n
|
defp blank?(n), do: n
|
||||||
|
|
||||||
def insert_or_update_user(data) do
|
def insert_or_update_user(data) do
|
||||||
data = data
|
data =
|
||||||
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|
data
|
||||||
|
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|
||||||
|
|
||||||
cs = User.remote_user_creation(data)
|
cs = User.remote_user_creation(data)
|
||||||
Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
|
Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,7 +18,14 @@ def insert(map, local \\ true) when is_map(map) do
|
||||||
with nil <- Activity.get_by_ap_id(map["id"]),
|
with nil <- Activity.get_by_ap_id(map["id"]),
|
||||||
map <- lazy_put_activity_defaults(map),
|
map <- lazy_put_activity_defaults(map),
|
||||||
:ok <- insert_full_object(map) do
|
: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)
|
Notification.create_notifications(activity)
|
||||||
stream_out(activity)
|
stream_out(activity)
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
@ -31,8 +38,10 @@ def insert(map, local \\ true) when is_map(map) do
|
||||||
def stream_out(activity) do
|
def stream_out(activity) do
|
||||||
if activity.data["type"] in ["Create", "Announce"] do
|
if activity.data["type"] in ["Create", "Announce"] do
|
||||||
Pleroma.Web.Streamer.stream("user", activity)
|
Pleroma.Web.Streamer.stream("user", activity)
|
||||||
|
|
||||||
if Enum.member?(activity.data["to"], "https://www.w3.org/ns/activitystreams#Public") do
|
if Enum.member?(activity.data["to"], "https://www.w3.org/ns/activitystreams#Public") do
|
||||||
Pleroma.Web.Streamer.stream("public", activity)
|
Pleroma.Web.Streamer.stream("public", activity)
|
||||||
|
|
||||||
if activity.local do
|
if activity.local do
|
||||||
Pleroma.Web.Streamer.stream("public:local", activity)
|
Pleroma.Web.Streamer.stream("public:local", activity)
|
||||||
end
|
end
|
||||||
|
@ -42,10 +51,15 @@ def stream_out(activity) do
|
||||||
|
|
||||||
def create(%{to: to, actor: actor, context: context, object: object} = params) do
|
def create(%{to: to, actor: actor, context: context, object: object} = params) do
|
||||||
additional = params[:additional] || %{}
|
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]
|
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, activity} <- insert(create_data, local),
|
||||||
:ok <- maybe_federate(activity) do
|
:ok <- maybe_federate(activity) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
@ -53,7 +67,8 @@ def create(%{to: to, actor: actor, context: context, object: object} = params) d
|
||||||
end
|
end
|
||||||
|
|
||||||
def accept(%{to: to, actor: actor, object: object} = params) do
|
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},
|
with data <- %{"to" => to, "type" => "Accept", "actor" => actor, "object" => object},
|
||||||
{:ok, activity} <- insert(data, local),
|
{:ok, activity} <- insert(data, local),
|
||||||
|
@ -63,9 +78,16 @@ def accept(%{to: to, actor: actor, object: object} = params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
|
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, activity} <- insert(data, local),
|
||||||
:ok <- maybe_federate(activity) do
|
:ok <- maybe_federate(activity) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
@ -73,7 +95,12 @@ def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: This is weird, maybe we shouldn't check here if we can make the activity.
|
# 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),
|
with nil <- get_existing_like(ap_id, object),
|
||||||
like_data <- make_like_data(user, object, activity_id),
|
like_data <- make_like_data(user, object, activity_id),
|
||||||
{:ok, activity} <- insert(like_data, local),
|
{:ok, activity} <- insert(like_data, local),
|
||||||
|
@ -91,11 +118,17 @@ def unlike(%User{} = actor, %Object{} = object) do
|
||||||
{:ok, _activity} <- Repo.delete(activity),
|
{:ok, _activity} <- Repo.delete(activity),
|
||||||
{:ok, object} <- remove_like_from_object(activity, object) do
|
{:ok, object} <- remove_like_from_object(activity, object) do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
else _e -> {:ok, object}
|
else
|
||||||
|
_e -> {:ok, object}
|
||||||
end
|
end
|
||||||
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),
|
with true <- is_public?(object),
|
||||||
announce_data <- make_announce_data(user, object, activity_id),
|
announce_data <- make_announce_data(user, object, activity_id),
|
||||||
{:ok, activity} <- insert(announce_data, local),
|
{: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),
|
with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
|
||||||
unfollow_data <- make_unfollow_data(follower, followed, follow_activity),
|
unfollow_data <- make_unfollow_data(follower, followed, follow_activity),
|
||||||
{:ok, activity} <- insert(unfollow_data, local),
|
{:ok, activity} <- insert(unfollow_data, local),
|
||||||
:ok, maybe_federate(activity) do
|
:ok,
|
||||||
|
maybe_federate(activity) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
|
def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
|
||||||
user = User.get_cached_by_ap_id(actor)
|
user = User.get_cached_by_ap_id(actor)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"type" => "Delete",
|
"type" => "Delete",
|
||||||
"actor" => actor,
|
"actor" => actor,
|
||||||
"object" => id,
|
"object" => id,
|
||||||
"to" => [user.follower_address, "https://www.w3.org/ns/activitystreams#Public"]
|
"to" => [user.follower_address, "https://www.w3.org/ns/activitystreams#Public"]
|
||||||
}
|
}
|
||||||
|
|
||||||
with Repo.delete(object),
|
with Repo.delete(object),
|
||||||
Repo.delete_all(Activity.all_non_create_by_object_ap_id_q(id)),
|
Repo.delete_all(Activity.all_non_create_by_object_ap_id_q(id)),
|
||||||
{:ok, activity} <- insert(data, local),
|
{: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
|
def fetch_activities_for_context(context, opts \\ %{}) do
|
||||||
public = ["https://www.w3.org/ns/activitystreams#Public"]
|
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
|
recipients =
|
||||||
query = query
|
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_blocked(opts)
|
||||||
|> restrict_recipients(recipients, opts["user"])
|
|> restrict_recipients(recipients, opts["user"])
|
||||||
|
|
||||||
query = from activity in query,
|
query =
|
||||||
where: fragment("?->>'type' = ? and ?->>'context' = ?", activity.data, "Create", activity.data, ^context),
|
from(
|
||||||
order_by: [desc: :id]
|
activity in query,
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"?->>'type' = ? and ?->>'context' = ?",
|
||||||
|
activity.data,
|
||||||
|
"Create",
|
||||||
|
activity.data,
|
||||||
|
^context
|
||||||
|
),
|
||||||
|
order_by: [desc: :id]
|
||||||
|
)
|
||||||
|
|
||||||
Repo.all(query)
|
Repo.all(query)
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Make this work properly with unlisted.
|
# TODO: Make this work properly with unlisted.
|
||||||
def fetch_public_activities(opts \\ %{}) do
|
def fetch_public_activities(opts \\ %{}) do
|
||||||
q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts)
|
q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts)
|
||||||
|
|
||||||
q
|
q
|
||||||
|> Repo.all
|
|> Repo.all()
|
||||||
|> Enum.reverse
|
|> Enum.reverse()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_since(query, %{"since_id" => since_id}) do
|
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
|
end
|
||||||
|
|
||||||
defp restrict_since(query, _), do: query
|
defp restrict_since(query, _), do: query
|
||||||
|
|
||||||
defp restrict_tag(query, %{"tag" => tag}) do
|
defp restrict_tag(query, %{"tag" => tag}) do
|
||||||
from activity in query,
|
from(
|
||||||
|
activity in query,
|
||||||
where: fragment("? <@ (? #> '{\"object\",\"tag\"}')", ^tag, activity.data)
|
where: fragment("? <@ (? #> '{\"object\",\"tag\"}')", ^tag, activity.data)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_tag(query, _), do: query
|
defp restrict_tag(query, _), do: query
|
||||||
|
|
||||||
defp restrict_recipients(query, [], user), do: query
|
defp restrict_recipients(query, [], user), do: query
|
||||||
|
|
||||||
defp restrict_recipients(query, recipients, nil) do
|
defp restrict_recipients(query, recipients, nil) do
|
||||||
from activity in query,
|
from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients))
|
||||||
where: fragment("? && ?", ^recipients, activity.recipients)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_recipients(query, recipients, user) do
|
defp restrict_recipients(query, recipients, user) do
|
||||||
from activity in query,
|
from(
|
||||||
|
activity in query,
|
||||||
where: fragment("? && ?", ^recipients, activity.recipients),
|
where: fragment("? && ?", ^recipients, activity.recipients),
|
||||||
or_where: activity.actor == ^user.ap_id
|
or_where: activity.actor == ^user.ap_id
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_limit(query, %{"limit" => limit}) do
|
defp restrict_limit(query, %{"limit" => limit}) do
|
||||||
from activity in query,
|
from(activity in query, limit: ^limit)
|
||||||
limit: ^limit
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_limit(query, _), do: query
|
defp restrict_limit(query, _), do: query
|
||||||
|
|
||||||
defp restrict_local(query, %{"local_only" => true}) do
|
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
|
end
|
||||||
|
|
||||||
defp restrict_local(query, _), do: query
|
defp restrict_local(query, _), do: query
|
||||||
|
|
||||||
defp restrict_max(query, %{"max_id" => max_id}) do
|
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
|
end
|
||||||
|
|
||||||
defp restrict_max(query, _), do: query
|
defp restrict_max(query, _), do: query
|
||||||
|
|
||||||
defp restrict_actor(query, %{"actor_id" => actor_id}) do
|
defp restrict_actor(query, %{"actor_id" => actor_id}) do
|
||||||
from activity in query,
|
from(activity in query, where: activity.actor == ^actor_id)
|
||||||
where: activity.actor == ^actor_id
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_actor(query, _), do: query
|
defp restrict_actor(query, _), do: query
|
||||||
|
|
||||||
defp restrict_type(query, %{"type" => type}) when is_binary(type) do
|
defp restrict_type(query, %{"type" => type}) when is_binary(type) do
|
||||||
restrict_type(query, %{"type" => [type]})
|
restrict_type(query, %{"type" => [type]})
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_type(query, %{"type" => type}) do
|
defp restrict_type(query, %{"type" => type}) do
|
||||||
from activity in query,
|
from(activity in query, where: fragment("?->>'type' = ANY(?)", activity.data, ^type))
|
||||||
where: fragment("?->>'type' = ANY(?)", activity.data, ^type)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_type(query, _), do: query
|
defp restrict_type(query, _), do: query
|
||||||
|
|
||||||
defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
|
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)
|
where: fragment("? <@ (? #> '{\"object\",\"likes\"}')", ^ap_id, activity.data)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_favorited_by(query, _), do: query
|
defp restrict_favorited_by(query, _), do: query
|
||||||
|
|
||||||
defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
|
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, ^[])
|
where: fragment("not (? #> '{\"object\",\"attachment\"}' = ?)", activity.data, ^[])
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_media(query, _), do: query
|
defp restrict_media(query, _), do: query
|
||||||
|
|
||||||
# Only search through last 100_000 activities by default
|
# Only search through last 100_000 activities by default
|
||||||
defp restrict_recent(query, %{"whole_db" => true}), do: query
|
defp restrict_recent(query, %{"whole_db" => true}), do: query
|
||||||
|
|
||||||
defp restrict_recent(query, _) do
|
defp restrict_recent(query, _) do
|
||||||
since = (Repo.aggregate(Activity, :max, :id) || 0) - 100_000
|
since = (Repo.aggregate(Activity, :max, :id) || 0) - 100_000
|
||||||
|
|
||||||
from activity in query,
|
from(activity in query, where: activity.id > ^since)
|
||||||
where: activity.id > ^since
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
|
defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
|
||||||
blocks = info["blocks"] || []
|
blocks = info["blocks"] || []
|
||||||
from activity in query,
|
from(activity in query, where: fragment("not (? = ANY(?))", activity.actor, ^blocks))
|
||||||
where: fragment("not (? = ANY(?))", activity.actor, ^blocks)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_blocked(query, _), do: query
|
defp restrict_blocked(query, _), do: query
|
||||||
|
|
||||||
def fetch_activities_query(recipients, opts \\ %{}) do
|
def fetch_activities_query(recipients, opts \\ %{}) do
|
||||||
base_query = from activity in Activity,
|
base_query =
|
||||||
limit: 20,
|
from(
|
||||||
order_by: [fragment("? desc nulls last", activity.id)]
|
activity in Activity,
|
||||||
|
limit: 20,
|
||||||
|
order_by: [fragment("? desc nulls last", activity.id)]
|
||||||
|
)
|
||||||
|
|
||||||
base_query
|
base_query
|
||||||
|> restrict_recipients(recipients, opts["user"])
|
|> restrict_recipients(recipients, opts["user"])
|
||||||
|
@ -266,8 +337,8 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
||||||
|
|
||||||
def fetch_activities(recipients, opts \\ %{}) do
|
def fetch_activities(recipients, opts \\ %{}) do
|
||||||
fetch_activities_query(recipients, opts)
|
fetch_activities_query(recipients, opts)
|
||||||
|> Repo.all
|
|> Repo.all()
|
||||||
|> Enum.reverse
|
|> Enum.reverse()
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload(file) do
|
def upload(file) do
|
||||||
|
@ -276,15 +347,19 @@ def upload(file) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_data_from_user_object(data) do
|
def user_data_from_user_object(data) do
|
||||||
avatar = data["icon"]["url"] && %{
|
avatar =
|
||||||
"type" => "Image",
|
data["icon"]["url"] &&
|
||||||
"url" => [%{"href" => data["icon"]["url"]}]
|
%{
|
||||||
}
|
"type" => "Image",
|
||||||
|
"url" => [%{"href" => data["icon"]["url"]}]
|
||||||
|
}
|
||||||
|
|
||||||
banner = data["image"]["url"] && %{
|
banner =
|
||||||
"type" => "Image",
|
data["image"]["url"] &&
|
||||||
"url" => [%{"href" => data["image"]["url"]}]
|
%{
|
||||||
}
|
"type" => "Image",
|
||||||
|
"url" => [%{"href" => data["image"]["url"]}]
|
||||||
|
}
|
||||||
|
|
||||||
user_data = %{
|
user_data = %{
|
||||||
ap_id: data["id"],
|
ap_id: data["id"],
|
||||||
|
@ -304,8 +379,9 @@ def user_data_from_user_object(data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_and_prepare_user_from_ap_id(ap_id) do
|
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"]),
|
with {:ok, %{status_code: 200, body: body}} <-
|
||||||
{:ok, data} <- Jason.decode(body) do
|
@httpoison.get(ap_id, Accept: "application/activity+json"),
|
||||||
|
{:ok, data} <- Jason.decode(body) do
|
||||||
user_data_from_user_object(data)
|
user_data_from_user_object(data)
|
||||||
else
|
else
|
||||||
e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
|
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
|
end
|
||||||
|
|
||||||
def publish(actor, activity) do
|
def publish(actor, activity) do
|
||||||
followers = if actor.follower_address in activity.recipients do
|
followers =
|
||||||
{:ok, followers} = User.get_followers(actor)
|
if actor.follower_address in activity.recipients do
|
||||||
followers |> Enum.filter(&(!&1.local))
|
{:ok, followers} = User.get_followers(actor)
|
||||||
else
|
followers |> Enum.filter(&(!&1.local))
|
||||||
[]
|
else
|
||||||
end
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
remote_inboxes = (Pleroma.Web.Salmon.remote_users(activity) ++ followers)
|
remote_inboxes =
|
||||||
|> Enum.filter(fn (user) -> User.ap_enabled?(user) end)
|
(Pleroma.Web.Salmon.remote_users(activity) ++ followers)
|
||||||
|> Enum.map(fn (%{info: %{"source_data" => data}}) ->
|
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|
||||||
(data["endpoints"] && data["endpoints"]["sharedInbox"]) || data["inbox"]
|
|> Enum.map(fn %{info: %{"source_data" => data}} ->
|
||||||
end)
|
(data["endpoints"] && data["endpoints"]["sharedInbox"]) || data["inbox"]
|
||||||
|> Enum.uniq
|
end)
|
||||||
|
|> Enum.uniq()
|
||||||
|
|
||||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
json = Jason.encode!(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"]})
|
Enum.each(remote_inboxes, fn inbox ->
|
||||||
end
|
Federator.enqueue(:publish_single_ap, %{
|
||||||
|
inbox: inbox,
|
||||||
|
json: json,
|
||||||
|
actor: actor,
|
||||||
|
id: activity.data["id"]
|
||||||
|
})
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
|
def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
|
||||||
Logger.info("Federating #{id} to #{inbox}")
|
Logger.info("Federating #{id} to #{inbox}")
|
||||||
host = URI.parse(inbox).host
|
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
|
end
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
|
@ -368,17 +460,34 @@ def fetch_object_from_id(id) do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
else
|
else
|
||||||
Logger.info("Fetching #{id} via AP")
|
Logger.info("Fetching #{id} via AP")
|
||||||
|
|
||||||
with true <- String.starts_with?(id, "http"),
|
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),
|
{:ok, data} <- Jason.decode(body),
|
||||||
nil <- Object.get_by_ap_id(data["id"]),
|
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, activity} <- Transmogrifier.handle_incoming(params) do
|
||||||
{:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
|
{:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
|
||||||
else
|
else
|
||||||
object = %Object{} -> {:ok, object}
|
object = %Object{} ->
|
||||||
|
{:ok, object}
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
|
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
|
||||||
|
|
||||||
case OStatus.fetch_activity_from_url(id) do
|
case OStatus.fetch_activity_from_url(id) do
|
||||||
{:ok, [activity | _]} -> {:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
|
{:ok, [activity | _]} -> {:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
|
||||||
e -> e
|
e -> e
|
||||||
|
@ -388,15 +497,17 @@ def fetch_object_from_id(id) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_public?(activity) do
|
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
|
end
|
||||||
|
|
||||||
def visible_for_user?(activity, nil) do
|
def visible_for_user?(activity, nil) do
|
||||||
is_public?(activity)
|
is_public?(activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
def visible_for_user?(activity, user) do
|
def visible_for_user?(activity, user) do
|
||||||
x = [user.ap_id | user.following]
|
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))
|
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
action_fallback :errors
|
action_fallback(:errors)
|
||||||
|
|
||||||
def user(conn, %{"nickname" => nickname}) do
|
def user(conn, %{"nickname" => nickname}) do
|
||||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
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),
|
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||||
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
|
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
|
||||||
{page, _} = Integer.parse(page)
|
{page, _} = Integer.parse(page)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_header("content-type", "application/activity+json")
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|> json(UserView.render("following.json", %{user: user, page: page}))
|
|> 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),
|
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||||
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
|
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
|
||||||
{page, _} = Integer.parse(page)
|
{page, _} = Integer.parse(page)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_header("content-type", "application/activity+json")
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|> json(UserView.render("followers.json", %{user: user, page: page}))
|
|> 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
|
||||||
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
|
# TODO: Ensure that this inbox is a recipient of the message
|
||||||
def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
|
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
|
def inbox(conn, params) do
|
||||||
headers = Enum.into(conn.req_headers, %{})
|
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")
|
Logger.info("Signature not from author, relayed message, fetching from source")
|
||||||
ActivityPub.fetch_object_from_id(params["object"]["id"])
|
ActivityPub.fetch_object_from_id(params["object"]["id"])
|
||||||
else
|
else
|
||||||
|
|
|
@ -25,21 +25,25 @@ def fix_object(object) do
|
||||||
|> fix_tag
|
|> fix_tag
|
||||||
end
|
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
|
case ActivityPub.fetch_object_from_id(in_reply_to_id) do
|
||||||
{:ok, replied_object} ->
|
{:ok, replied_object} ->
|
||||||
activity = Activity.get_create_activity_by_object_ap_id(replied_object.data["id"])
|
activity = Activity.get_create_activity_by_object_ap_id(replied_object.data["id"])
|
||||||
|
|
||||||
object
|
object
|
||||||
|> Map.put("inReplyTo", replied_object.data["id"])
|
|> Map.put("inReplyTo", replied_object.data["id"])
|
||||||
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
|
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
|
||||||
|> Map.put("inReplyToStatusId", activity.id)
|
|> Map.put("inReplyToStatusId", activity.id)
|
||||||
|> Map.put("conversation", replied_object.data["context"] || object["conversation"])
|
|> Map.put("conversation", replied_object.data["context"] || object["conversation"])
|
||||||
|> Map.put("context", replied_object.data["context"] || object["conversation"])
|
|> Map.put("context", replied_object.data["context"] || object["conversation"])
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
|
Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
|
||||||
object
|
object
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_in_reply_to(object), do: object
|
def fix_in_reply_to(object), do: object
|
||||||
|
|
||||||
def fix_context(object) do
|
def fix_context(object) do
|
||||||
|
@ -48,27 +52,32 @@ def fix_context(object) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_attachments(object) do
|
def fix_attachments(object) do
|
||||||
attachments = (object["attachment"] || [])
|
attachments =
|
||||||
|> Enum.map(fn (data) ->
|
(object["attachment"] || [])
|
||||||
url = [%{"type" => "Link", "mediaType" => data["mediaType"], "href" => data["url"]}]
|
|> Enum.map(fn data ->
|
||||||
Map.put(data, "url", url)
|
url = [%{"type" => "Link", "mediaType" => data["mediaType"], "href" => data["url"]}]
|
||||||
end)
|
Map.put(data, "url", url)
|
||||||
|
end)
|
||||||
|
|
||||||
object
|
object
|
||||||
|> Map.put("attachment", attachments)
|
|> Map.put("attachment", attachments)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_emoji(object) do
|
def fix_emoji(object) do
|
||||||
tags = (object["tag"] || [])
|
tags = object["tag"] || []
|
||||||
emoji = tags |> Enum.filter(fn (data) -> data["type"] == "Emoji" and data["icon"] end)
|
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
|
|
||||||
|
|
||||||
mapping |> Map.put(name, data["icon"]["url"])
|
emoji =
|
||||||
end)
|
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
|
# we merge mastodon and pleroma emoji into a single mapping, to allow for both wire formats
|
||||||
emoji = Map.merge(object["emoji"] || %{}, emoji)
|
emoji = Map.merge(object["emoji"] || %{}, emoji)
|
||||||
|
@ -78,9 +87,10 @@ def fix_emoji(object) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_tag(object) do
|
def fix_tag(object) do
|
||||||
tags = (object["tag"] || [])
|
tags =
|
||||||
|> Enum.filter(fn (data) -> data["type"] == "Hashtag" and data["name"] end)
|
(object["tag"] || [])
|
||||||
|> Enum.map(fn (data) -> String.slice(data["name"], 1..-1) end)
|
|> 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
|
combined = (object["tag"] || []) ++ tags
|
||||||
|
|
||||||
|
@ -103,13 +113,13 @@ def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = obje
|
||||||
context: object["conversation"],
|
context: object["conversation"],
|
||||||
local: false,
|
local: false,
|
||||||
published: data["published"],
|
published: data["published"],
|
||||||
additional: Map.take(data, [
|
additional:
|
||||||
"cc",
|
Map.take(data, [
|
||||||
"id"
|
"cc",
|
||||||
])
|
"id"
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ActivityPub.create(params)
|
ActivityPub.create(params)
|
||||||
else
|
else
|
||||||
%Activity{} = activity -> {:ok, activity}
|
%Activity{} = activity -> {:ok, activity}
|
||||||
|
@ -117,11 +127,14 @@ def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = obje
|
||||||
end
|
end
|
||||||
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),
|
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
|
||||||
%User{} = follower <- User.get_or_fetch_by_ap_id(follower),
|
%User{} = follower <- User.get_or_fetch_by_ap_id(follower),
|
||||||
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
|
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
|
||||||
ActivityPub.accept(%{to: [follower.ap_id], actor: followed.ap_id, object: data, local: true})
|
ActivityPub.accept(%{to: [follower.ap_id], actor: followed.ap_id, object: data, local: true})
|
||||||
|
|
||||||
User.follow(follower, followed)
|
User.follow(follower, followed)
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
|
@ -129,7 +142,9 @@ def handle_incoming(%{"type" => "Follow", "object" => followed, "actor" => follo
|
||||||
end
|
end
|
||||||
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),
|
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, object} <- get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||||
{:ok, activity, object} <- ActivityPub.like(actor, object, id, false) do
|
{: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
|
||||||
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),
|
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, object} <- get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||||
{:ok, activity, object} <- ActivityPub.announce(actor, object, id, false) do
|
{: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
|
||||||
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
|
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)
|
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
|
||||||
|
|
||||||
banner = new_user_data[:info]["banner"]
|
banner = new_user_data[:info]["banner"]
|
||||||
update_data = new_user_data
|
|
||||||
|> Map.take([:name, :bio, :avatar])
|
update_data =
|
||||||
|> Map.put(:info, Map.merge(actor.info, %{"banner" => banner}))
|
new_user_data
|
||||||
|
|> Map.take([:name, :bio, :avatar])
|
||||||
|
|> Map.put(:info, Map.merge(actor.info, %{"banner" => banner}))
|
||||||
|
|
||||||
actor
|
actor
|
||||||
|> User.upgrade_changeset(update_data)
|
|> User.upgrade_changeset(update_data)
|
||||||
|> User.update_and_set_cache()
|
|> 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
|
else
|
||||||
e ->
|
e ->
|
||||||
Logger.error(e)
|
Logger.error(e)
|
||||||
|
@ -171,11 +199,15 @@ def handle_incoming(%{"type" => "Update", "object" => %{"type" => "Person"} = ob
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Make secure.
|
# TODO: Make secure.
|
||||||
def handle_incoming(%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data) do
|
def handle_incoming(
|
||||||
object_id = case object_id do
|
%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data
|
||||||
%{"id" => id} -> id
|
) do
|
||||||
id -> id
|
object_id =
|
||||||
end
|
case object_id do
|
||||||
|
%{"id" => id} -> id
|
||||||
|
id -> id
|
||||||
|
end
|
||||||
|
|
||||||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
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, object} <- get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||||
{:ok, activity} <- ActivityPub.delete(object, false) do
|
{:ok, activity} <- ActivityPub.delete(object, false) do
|
||||||
|
@ -203,6 +235,7 @@ def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) do
|
||||||
_e -> object
|
_e -> object
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_reply_to_uri(obj), do: obj
|
def set_reply_to_uri(obj), do: obj
|
||||||
|
|
||||||
# Prepares the object of an outgoing create activity.
|
# Prepares the object of an outgoing create activity.
|
||||||
|
@ -222,20 +255,25 @@ def prepare_object(object) do
|
||||||
"""
|
"""
|
||||||
internal -> Mastodon
|
internal -> Mastodon
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
|
def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
|
||||||
object = object
|
object =
|
||||||
|> prepare_object
|
object
|
||||||
data = data
|
|> prepare_object
|
||||||
|> Map.put("object", object)
|
|
||||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
data =
|
||||||
|
data
|
||||||
|
|> Map.put("object", object)
|
||||||
|
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||||
|
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare_outgoing(%{"type" => type} = data) do
|
def prepare_outgoing(%{"type" => type} = data) do
|
||||||
data = data
|
data =
|
||||||
|> maybe_fix_object_url
|
data
|
||||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
|> maybe_fix_object_url
|
||||||
|
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||||
|
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
end
|
end
|
||||||
|
@ -245,11 +283,13 @@ def maybe_fix_object_url(data) do
|
||||||
case ActivityPub.fetch_object_from_id(data["object"]) do
|
case ActivityPub.fetch_object_from_id(data["object"]) do
|
||||||
{:ok, relative_object} ->
|
{:ok, relative_object} ->
|
||||||
if relative_object.data["external_url"] do
|
if relative_object.data["external_url"] do
|
||||||
data = data
|
data =
|
||||||
|> Map.put("object", relative_object.data["external_url"])
|
data
|
||||||
|
|> Map.put("object", relative_object.data["external_url"])
|
||||||
else
|
else
|
||||||
data
|
data
|
||||||
end
|
end
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
Logger.error("Couldn't fetch #{data["object"]} #{inspect(e)}")
|
Logger.error("Couldn't fetch #{data["object"]} #{inspect(e)}")
|
||||||
data
|
data
|
||||||
|
@ -260,8 +300,15 @@ def maybe_fix_object_url(data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_hashtags(object) do
|
def add_hashtags(object) do
|
||||||
tags = (object["tag"] || [])
|
tags =
|
||||||
|> Enum.map fn (tag) -> %{"href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}", "name" => "##{tag}", "type" => "Hashtag"} end
|
(object["tag"] || [])
|
||||||
|
|> Enum.map(fn tag ->
|
||||||
|
%{
|
||||||
|
"href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}",
|
||||||
|
"name" => "##{tag}",
|
||||||
|
"type" => "Hashtag"
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
object
|
object
|
||||||
|> Map.put("tag", tags)
|
|> Map.put("tag", tags)
|
||||||
|
@ -269,10 +316,14 @@ def add_hashtags(object) do
|
||||||
|
|
||||||
def add_mention_tags(object) do
|
def add_mention_tags(object) do
|
||||||
recipients = object["to"] ++ (object["cc"] || [])
|
recipients = object["to"] ++ (object["cc"] || [])
|
||||||
mentions = recipients
|
|
||||||
|> Enum.map(fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end)
|
mentions =
|
||||||
|> Enum.filter(&(&1))
|
recipients
|
||||||
|> Enum.map(fn(user) -> %{"type" => "Mention", "href" => user.ap_id, "name" => "@#{user.nickname}"} end)
|
|> 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"] || []
|
tags = object["tag"] || []
|
||||||
|
|
||||||
|
@ -284,13 +335,18 @@ def add_mention_tags(object) do
|
||||||
def add_emoji_tags(object) do
|
def add_emoji_tags(object) do
|
||||||
tags = object["tag"] || []
|
tags = object["tag"] || []
|
||||||
emoji = object["emoji"] || []
|
emoji = object["emoji"] || []
|
||||||
out = emoji |> Enum.map(fn {name, url} ->
|
|
||||||
%{"icon" => %{"url" => url, "type" => "Image"},
|
out =
|
||||||
"name" => ":" <> name <> ":",
|
emoji
|
||||||
"type" => "Emoji",
|
|> Enum.map(fn {name, url} ->
|
||||||
"updated" => "1970-01-01T00:00:00Z",
|
%{
|
||||||
"id" => url}
|
"icon" => %{"url" => url, "type" => "Image"},
|
||||||
end)
|
"name" => ":" <> name <> ":",
|
||||||
|
"type" => "Emoji",
|
||||||
|
"updated" => "1970-01-01T00:00:00Z",
|
||||||
|
"id" => url
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
object
|
object
|
||||||
|> Map.put("tag", tags ++ out)
|
|> Map.put("tag", tags ++ out)
|
||||||
|
@ -313,11 +369,12 @@ def add_attributed_to(object) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare_attachments(object) do
|
def prepare_attachments(object) do
|
||||||
attachments = (object["attachment"] || [])
|
attachments =
|
||||||
|> Enum.map(fn (data) ->
|
(object["attachment"] || [])
|
||||||
[%{"mediaType" => media_type, "href" => href} | _] = data["url"]
|
|> Enum.map(fn data ->
|
||||||
%{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"}
|
[%{"mediaType" => media_type, "href" => href} | _] = data["url"]
|
||||||
end)
|
%{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"}
|
||||||
|
end)
|
||||||
|
|
||||||
object
|
object
|
||||||
|> Map.put("attachment", attachments)
|
|> Map.put("attachment", attachments)
|
||||||
|
@ -325,9 +382,24 @@ def prepare_attachments(object) do
|
||||||
|
|
||||||
defp user_upgrade_task(user) do
|
defp user_upgrade_task(user) do
|
||||||
old_follower_address = User.ap_followers(user)
|
old_follower_address = User.ap_followers(user)
|
||||||
q = from u in User,
|
|
||||||
where: ^old_follower_address in u.following,
|
q =
|
||||||
update: [set: [following: fragment("array_replace(?,?,?)", u.following, ^old_follower_address, ^user.follower_address)]]
|
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, [])
|
Repo.update_all(q, [])
|
||||||
|
|
||||||
maybe_retire_websub(user.ap_id)
|
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 do this for recent activties, don't go through the whole db.
|
||||||
# Only look at the last 1000 activities.
|
# Only look at the last 1000 activities.
|
||||||
since = (Repo.aggregate(Activity, :max, :id) || 0) - 1_000
|
since = (Repo.aggregate(Activity, :max, :id) || 0) - 1_000
|
||||||
q = from a in Activity,
|
|
||||||
where: ^old_follower_address in a.recipients,
|
q =
|
||||||
where: a.id > ^since,
|
from(
|
||||||
update: [set: [recipients: fragment("array_replace(?,?,?)", a.recipients, ^old_follower_address, ^user.follower_address)]]
|
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, [])
|
Repo.update_all(q, [])
|
||||||
end
|
end
|
||||||
|
|
||||||
def upgrade_user_from_ap_id(ap_id, async \\ true) do
|
def upgrade_user_from_ap_id(ap_id, async \\ true) do
|
||||||
with %User{local: false} = user <- User.get_by_ap_id(ap_id),
|
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
|
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||||
data = data
|
data =
|
||||||
|> Map.put(:info, Map.merge(user.info, data[:info]))
|
data
|
||||||
|
|> Map.put(:info, Map.merge(user.info, data[:info]))
|
||||||
|
|
||||||
already_ap = User.ap_enabled?(user)
|
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
|
if !already_ap do
|
||||||
# This could potentially take a long time, do it in the background
|
# 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
|
def maybe_retire_websub(ap_id) do
|
||||||
# some sanity checks
|
# some sanity checks
|
||||||
if is_binary(ap_id) && (String.length(ap_id) > 8) do
|
if is_binary(ap_id) && String.length(ap_id) > 8 do
|
||||||
q = from ws in Pleroma.Web.Websub.WebsubClientSubscription,
|
q =
|
||||||
where: fragment("? like ?", ws.topic, ^"#{ap_id}%")
|
from(
|
||||||
|
ws in Pleroma.Web.Websub.WebsubClientSubscription,
|
||||||
|
where: fragment("? like ?", ws.topic, ^"#{ap_id}%")
|
||||||
|
)
|
||||||
|
|
||||||
Repo.delete_all(q)
|
Repo.delete_all(q)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,7 +26,7 @@ def make_json_ld_header do
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_date do
|
def make_date do
|
||||||
DateTime.utc_now() |> DateTime.to_iso8601
|
DateTime.utc_now() |> DateTime.to_iso8601()
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_activity_id do
|
def generate_activity_id do
|
||||||
|
@ -38,25 +38,28 @@ def generate_context_id do
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_object_id do
|
def generate_object_id do
|
||||||
Helpers.o_status_url(Endpoint, :object, UUID.generate)
|
Helpers.o_status_url(Endpoint, :object, UUID.generate())
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_id(type) do
|
def generate_id(type) do
|
||||||
"#{Web.base_url()}/#{type}/#{UUID.generate}"
|
"#{Web.base_url()}/#{type}/#{UUID.generate()}"
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Enqueues an activity for federation if it's local
|
Enqueues an activity for federation if it's local
|
||||||
"""
|
"""
|
||||||
def maybe_federate(%Activity{local: true} = activity) do
|
def maybe_federate(%Activity{local: true} = activity) do
|
||||||
priority = case activity.data["type"] do
|
priority =
|
||||||
"Delete" -> 10
|
case activity.data["type"] do
|
||||||
"Create" -> 1
|
"Delete" -> 10
|
||||||
_ -> 5
|
"Create" -> 1
|
||||||
end
|
_ -> 5
|
||||||
|
end
|
||||||
|
|
||||||
Pleroma.Web.Federator.enqueue(:publish, activity, priority)
|
Pleroma.Web.Federator.enqueue(:publish, activity, priority)
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_federate(_), do: :ok
|
def maybe_federate(_), do: :ok
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -64,9 +67,10 @@ def maybe_federate(_), do: :ok
|
||||||
also adds it to an included object
|
also adds it to an included object
|
||||||
"""
|
"""
|
||||||
def lazy_put_activity_defaults(map) do
|
def lazy_put_activity_defaults(map) do
|
||||||
map = map
|
map =
|
||||||
|> Map.put_new_lazy("id", &generate_activity_id/0)
|
map
|
||||||
|> Map.put_new_lazy("published", &make_date/0)
|
|> Map.put_new_lazy("id", &generate_activity_id/0)
|
||||||
|
|> Map.put_new_lazy("published", &make_date/0)
|
||||||
|
|
||||||
if is_map(map["object"]) do
|
if is_map(map["object"]) do
|
||||||
object = lazy_put_object_defaults(map["object"])
|
object = lazy_put_object_defaults(map["object"])
|
||||||
|
@ -88,11 +92,13 @@ def lazy_put_object_defaults(map) do
|
||||||
@doc """
|
@doc """
|
||||||
Inserts a full object if it is contained in an activity.
|
Inserts a full object if it is contained in an activity.
|
||||||
"""
|
"""
|
||||||
def insert_full_object(%{"object" => %{"type" => type} = object_data}) 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
|
with {:ok, _} <- Object.create(object_data) do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def insert_full_object(_), do: :ok
|
def insert_full_object(_), do: :ok
|
||||||
|
|
||||||
def update_object_in_activities(%{data: %{"id" => id}} = object) do
|
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
|
# Alternatively, just don't do this and fetch the current object each time. Most
|
||||||
# could probably be taken from cache.
|
# could probably be taken from cache.
|
||||||
relevant_activities = Activity.all_by_object_ap_id(id)
|
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)
|
new_activity_data = activity.data |> Map.put("object", object.data)
|
||||||
changeset = Changeset.change(activity, data: new_activity_data)
|
changeset = Changeset.change(activity, data: new_activity_data)
|
||||||
Repo.update(changeset)
|
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
|
Returns an existing like if a user already liked an object
|
||||||
"""
|
"""
|
||||||
def get_existing_like(actor, %{data: %{"id" => id}}) do
|
def get_existing_like(actor, %{data: %{"id" => id}}) do
|
||||||
query = from activity in Activity,
|
query =
|
||||||
where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
|
from(
|
||||||
# this is to use the index
|
activity in Activity,
|
||||||
where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ?", activity.data, activity.data, ^id),
|
where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
|
||||||
where: fragment("(?)->>'type' = 'Like'", activity.data)
|
# 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)
|
Repo.one(query)
|
||||||
end
|
end
|
||||||
|
@ -137,10 +153,12 @@ def make_like_data(%User{ap_id: ap_id} = actor, %{data: %{"id" => id}} = object,
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_element_in_object(property, element, object) do
|
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),
|
changeset <- Changeset.change(object, data: new_data),
|
||||||
{:ok, object} <- Repo.update(changeset),
|
{:ok, object} <- Repo.update(changeset),
|
||||||
_ <- update_object_in_activities(object) do
|
_ <- update_object_in_activities(object) do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -150,7 +168,7 @@ def update_likes_in_object(likes, object) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do
|
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)
|
update_likes_in_object(likes, object)
|
||||||
end
|
end
|
||||||
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
|
if activity_id, do: Map.put(data, "id", activity_id), else: data
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_latest_follow(%User{ap_id: follower_id},
|
def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do
|
||||||
%User{ap_id: followed_id}) do
|
query =
|
||||||
query = from activity in Activity,
|
from(
|
||||||
where: fragment("? @> ?", activity.data, ^%{type: "Follow", actor: follower_id,
|
activity in Activity,
|
||||||
object: followed_id}),
|
where:
|
||||||
order_by: [desc: :id],
|
fragment(
|
||||||
limit: 1
|
"? @> ?",
|
||||||
|
activity.data,
|
||||||
|
^%{type: "Follow", actor: follower_id, object: followed_id}
|
||||||
|
),
|
||||||
|
order_by: [desc: :id],
|
||||||
|
limit: 1
|
||||||
|
)
|
||||||
|
|
||||||
Repo.one(query)
|
Repo.one(query)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -193,7 +218,11 @@ def fetch_latest_follow(%User{ap_id: follower_id},
|
||||||
@doc """
|
@doc """
|
||||||
Make announce activity data for the given actor and object
|
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 = %{
|
data = %{
|
||||||
"type" => "Announce",
|
"type" => "Announce",
|
||||||
"actor" => ap_id,
|
"actor" => ap_id,
|
||||||
|
@ -207,7 +236,7 @@ def make_announce_data(%User{ap_id: ap_id} = user, %Object{data: %{"id" => id}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_announce_to_object(%Activity{data: %{"actor" => actor}}, object) do
|
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)
|
update_element_in_object("announcement", announcements, object)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -223,14 +252,14 @@ def make_unfollow_data(follower, followed, follow_activity) do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
#### Create-related helpers
|
#### Create-related helpers
|
||||||
|
|
||||||
def make_create_data(params, additional) do
|
def make_create_data(params, additional) do
|
||||||
published = params.published || make_date()
|
published = params.published || make_date()
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
"to" => params.to |> Enum.uniq,
|
"to" => params.to |> Enum.uniq(),
|
||||||
"actor" => params.actor.ap_id,
|
"actor" => params.actor.ap_id,
|
||||||
"object" => params.object,
|
"object" => params.object,
|
||||||
"published" => published,
|
"published" => published,
|
||||||
|
|
|
@ -12,6 +12,7 @@ def render("user.json", %{user: user}) do
|
||||||
{:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"])
|
{:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"])
|
||||||
public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
|
public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
|
||||||
public_key = :public_key.pem_encode([public_key])
|
public_key = :public_key.pem_encode([public_key])
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"id" => user.ap_id,
|
"id" => user.ap_id,
|
||||||
"type" => "Person",
|
"type" => "Person",
|
||||||
|
@ -30,7 +31,7 @@ def render("user.json", %{user: user}) do
|
||||||
"publicKeyPem" => public_key
|
"publicKeyPem" => public_key
|
||||||
},
|
},
|
||||||
"endpoints" => %{
|
"endpoints" => %{
|
||||||
"sharedInbox" => "#{Pleroma.Web.Endpoint.url}/inbox"
|
"sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox"
|
||||||
},
|
},
|
||||||
"icon" => %{
|
"icon" => %{
|
||||||
"type" => "Image",
|
"type" => "Image",
|
||||||
|
@ -47,7 +48,8 @@ def render("user.json", %{user: user}) do
|
||||||
def collection(collection, iri, page) do
|
def collection(collection, iri, page) do
|
||||||
offset = (page - 1) * 10
|
offset = (page - 1) * 10
|
||||||
items = Enum.slice(collection, offset, 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 = %{
|
map = %{
|
||||||
"id" => "#{iri}?page=#{page}",
|
"id" => "#{iri}?page=#{page}",
|
||||||
"type" => "OrderedCollectionPage",
|
"type" => "OrderedCollectionPage",
|
||||||
|
@ -55,19 +57,22 @@ def collection(collection, iri, page) do
|
||||||
"totalItems" => length(collection),
|
"totalItems" => length(collection),
|
||||||
"orderedItems" => items
|
"orderedItems" => items
|
||||||
}
|
}
|
||||||
|
|
||||||
if offset < length(collection) do
|
if offset < length(collection) do
|
||||||
Map.put(map, "next", "#{iri}?page=#{page+1}")
|
Map.put(map, "next", "#{iri}?page=#{page + 1}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("following.json", %{user: user, page: page}) do
|
def render("following.json", %{user: user, page: page}) do
|
||||||
{:ok, following} = User.get_friends(user)
|
{:ok, following} = User.get_friends(user)
|
||||||
|
|
||||||
collection(following, "#{user.ap_id}/following", page)
|
collection(following, "#{user.ap_id}/following", page)
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("following.json", %{user: user}) do
|
def render("following.json", %{user: user}) do
|
||||||
{:ok, following} = User.get_friends(user)
|
{:ok, following} = User.get_friends(user)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"id" => "#{user.ap_id}/following",
|
"id" => "#{user.ap_id}/following",
|
||||||
"type" => "OrderedCollection",
|
"type" => "OrderedCollection",
|
||||||
|
@ -79,12 +84,14 @@ def render("following.json", %{user: user}) do
|
||||||
|
|
||||||
def render("followers.json", %{user: user, page: page}) do
|
def render("followers.json", %{user: user, page: page}) do
|
||||||
{:ok, followers} = User.get_followers(user)
|
{:ok, followers} = User.get_followers(user)
|
||||||
|
|
||||||
collection(followers, "#{user.ap_id}/followers", page)
|
collection(followers, "#{user.ap_id}/followers", page)
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("followers.json", %{user: user}) do
|
def render("followers.json", %{user: user}) do
|
||||||
{:ok, followers} = User.get_followers(user)
|
{:ok, followers} = User.get_followers(user)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"id" => "#{user.ap_id}/followers",
|
"id" => "#{user.ap_id}/followers",
|
||||||
"type" => "OrderedCollection",
|
"type" => "OrderedCollection",
|
||||||
|
@ -115,19 +122,21 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do
|
||||||
activities = Enum.reverse(activities)
|
activities = Enum.reverse(activities)
|
||||||
max_id = Enum.at(activities, 0).id
|
max_id = Enum.at(activities, 0).id
|
||||||
|
|
||||||
collection = Enum.map(activities, fn (act) ->
|
collection =
|
||||||
{:ok, data} = Transmogrifier.prepare_outgoing(act.data)
|
Enum.map(activities, fn act ->
|
||||||
data
|
{:ok, data} = Transmogrifier.prepare_outgoing(act.data)
|
||||||
end)
|
data
|
||||||
|
end)
|
||||||
|
|
||||||
iri = "#{user.ap_id}/outbox"
|
iri = "#{user.ap_id}/outbox"
|
||||||
|
|
||||||
page = %{
|
page = %{
|
||||||
"id" => "#{iri}?max_id=#{max_id}",
|
"id" => "#{iri}?max_id=#{max_id}",
|
||||||
"type" => "OrderedCollectionPage",
|
"type" => "OrderedCollectionPage",
|
||||||
"partOf" => iri,
|
"partOf" => iri,
|
||||||
"totalItems" => info.note_count,
|
"totalItems" => info.note_count,
|
||||||
"orderedItems" => collection,
|
"orderedItems" => collection,
|
||||||
"next" => "#{iri}?max_id=#{min_id-1}",
|
"next" => "#{iri}?max_id=#{min_id - 1}"
|
||||||
}
|
}
|
||||||
|
|
||||||
if max_qid == nil do
|
if max_qid == nil do
|
||||||
|
|
|
@ -6,11 +6,11 @@ defmodule Pleroma.Web.UserSocket do
|
||||||
## Channels
|
## Channels
|
||||||
# channel "room:*", Pleroma.Web.RoomChannel
|
# channel "room:*", Pleroma.Web.RoomChannel
|
||||||
if Application.get_env(:pleroma, :chat) |> Keyword.get(:enabled) do
|
if Application.get_env(:pleroma, :chat) |> Keyword.get(:enabled) do
|
||||||
channel "chat:*", Pleroma.Web.ChatChannel
|
channel("chat:*", Pleroma.Web.ChatChannel)
|
||||||
end
|
end
|
||||||
|
|
||||||
## Transports
|
## Transports
|
||||||
transport :websocket, Phoenix.Transports.WebSocket
|
transport(:websocket, Phoenix.Transports.WebSocket)
|
||||||
# transport :longpoll, Phoenix.Transports.LongPoll
|
# transport :longpoll, Phoenix.Transports.LongPoll
|
||||||
|
|
||||||
# Socket params are passed from the client and can
|
# Socket params are passed from the client and can
|
||||||
|
|
|
@ -9,19 +9,21 @@ def join("chat:public", _message, socket) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(:after_join, socket) do
|
def handle_info(:after_join, socket) do
|
||||||
push socket, "messages", %{messages: ChatChannelState.messages()}
|
push(socket, "messages", %{messages: ChatChannelState.messages()})
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} = socket) do
|
def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} = socket) do
|
||||||
text = String.trim(text)
|
text = String.trim(text)
|
||||||
|
|
||||||
if String.length(text) > 0 do
|
if String.length(text) > 0 do
|
||||||
author = User.get_cached_by_nickname(user_name)
|
author = User.get_cached_by_nickname(user_name)
|
||||||
author = Pleroma.Web.MastodonAPI.AccountView.render("account.json", user: author)
|
author = Pleroma.Web.MastodonAPI.AccountView.render("account.json", user: author)
|
||||||
message = ChatChannelState.add_message(%{text: text, author: author})
|
message = ChatChannelState.add_message(%{text: text, author: author})
|
||||||
|
|
||||||
broadcast! socket, "new_msg", message
|
broadcast!(socket, "new_msg", message)
|
||||||
end
|
end
|
||||||
|
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -43,6 +45,6 @@ def add_message(message) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def messages() do
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
def delete(activity_id, user) do
|
def delete(activity_id, user) do
|
||||||
with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id),
|
with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id),
|
||||||
%Object{} = object <- Object.get_by_ap_id(object_id),
|
%Object{} = object <- Object.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} <- ActivityPub.delete(object) do
|
||||||
{:ok, delete}
|
{:ok, delete}
|
||||||
end
|
end
|
||||||
|
@ -46,17 +46,22 @@ def unfavorite(id_or_ap_id, user) do
|
||||||
end
|
end
|
||||||
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
|
def get_visibility(%{"in_reply_to_status_id" => status_id}) when not is_nil(status_id) do
|
||||||
inReplyTo = get_replied_to_activity(status_id)
|
inReplyTo = get_replied_to_activity(status_id)
|
||||||
Pleroma.Web.MastodonAPI.StatusView.get_visibility(inReplyTo.data["object"])
|
Pleroma.Web.MastodonAPI.StatusView.get_visibility(inReplyTo.data["object"])
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_visibility(_), do: "public"
|
def get_visibility(_), do: "public"
|
||||||
|
|
||||||
@instance Application.get_env(:pleroma, :instance)
|
@instance Application.get_env(:pleroma, :instance)
|
||||||
@limit Keyword.get(@instance, :limit)
|
@limit Keyword.get(@instance, :limit)
|
||||||
def post(user, %{"status" => status} = data) do
|
def post(user, %{"status" => status} = data) do
|
||||||
visibility = get_visibility(data)
|
visibility = get_visibility(data)
|
||||||
|
|
||||||
with status <- String.trim(status),
|
with status <- String.trim(status),
|
||||||
length when length in 1..@limit <- String.length(status),
|
length when length in 1..@limit <- String.length(status),
|
||||||
attachments <- attachments_from_ids(data["media_ids"]),
|
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"]),
|
inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]),
|
||||||
{to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility),
|
{to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility),
|
||||||
tags <- Formatter.parse_tags(status, data),
|
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),
|
context <- make_context(inReplyTo),
|
||||||
cw <- data["spoiler_text"],
|
cw <- data["spoiler_text"],
|
||||||
object <- make_note_data(user.ap_id, to, context, content_html, attachments, inReplyTo, tags, cw, cc),
|
object <-
|
||||||
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
|
make_note_data(
|
||||||
res = ActivityPub.create(%{to: to, actor: user, context: context, object: object, additional: %{"cc" => cc}})
|
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)
|
User.increase_note_count(user)
|
||||||
res
|
res
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update(user) do
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||||
# This is a hack for twidere.
|
# This is a hack for twidere.
|
||||||
def get_by_id_or_ap_id(id) do
|
def get_by_id_or_ap_id(id) do
|
||||||
activity = Repo.get(Activity, id) || Activity.get_create_activity_by_object_ap_id(id)
|
activity = Repo.get(Activity, id) || Activity.get_create_activity_by_object_ap_id(id)
|
||||||
|
|
||||||
if activity.data["type"] == "Create" do
|
if activity.data["type"] == "Create" do
|
||||||
activity
|
activity
|
||||||
else
|
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
|
def get_replied_to_activity(id) when not is_nil(id) do
|
||||||
Repo.get(Activity, id)
|
Repo.get(Activity, id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_replied_to_activity(_), do: nil
|
def get_replied_to_activity(_), do: nil
|
||||||
|
|
||||||
def attachments_from_ids(ids) do
|
def attachments_from_ids(ids) do
|
||||||
Enum.map(ids || [], fn (media_id) ->
|
Enum.map(ids || [], fn media_id ->
|
||||||
Repo.get(Object, media_id).data
|
Repo.get(Object, media_id).data
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -27,8 +29,9 @@ def attachments_from_ids(ids) do
|
||||||
def to_for_user_and_mentions(user, mentions, inReplyTo, "public") do
|
def to_for_user_and_mentions(user, mentions, inReplyTo, "public") do
|
||||||
to = ["https://www.w3.org/ns/activitystreams#Public"]
|
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]
|
cc = [user.follower_address | mentioned_users]
|
||||||
|
|
||||||
if inReplyTo do
|
if inReplyTo do
|
||||||
{to, Enum.uniq([inReplyTo.data["actor"] | cc])}
|
{to, Enum.uniq([inReplyTo.data["actor"] | cc])}
|
||||||
else
|
else
|
||||||
|
@ -47,7 +50,8 @@ def to_for_user_and_mentions(user, mentions, inReplyTo, "private") do
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_for_user_and_mentions(user, mentions, inReplyTo, "direct") do
|
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
|
if inReplyTo do
|
||||||
{Enum.uniq([inReplyTo.data["actor"] | mentioned_users]), []}
|
{Enum.uniq([inReplyTo.data["actor"] | mentioned_users]), []}
|
||||||
else
|
else
|
||||||
|
@ -62,55 +66,72 @@ def make_content_html(status, mentions, attachments, tags, no_attachment_links \
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_context(%Activity{data: %{"context" => context}}), do: context
|
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 = true), do: text
|
||||||
|
|
||||||
def maybe_add_attachments(text, attachments, _no_links) do
|
def maybe_add_attachments(text, attachments, _no_links) do
|
||||||
add_attachments(text, attachments)
|
add_attachments(text, attachments)
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_attachments(text, attachments) do
|
def add_attachments(text, attachments) do
|
||||||
attachment_text = Enum.map(attachments, fn
|
attachment_text =
|
||||||
(%{"url" => [%{"href" => href} | _]}) ->
|
Enum.map(attachments, fn
|
||||||
name = URI.decode(Path.basename(href))
|
%{"url" => [%{"href" => href} | _]} ->
|
||||||
"<a href=\"#{href}\" class='attachment'>#{shortname(name)}</a>"
|
name = URI.decode(Path.basename(href))
|
||||||
_ -> ""
|
"<a href=\"#{href}\" class='attachment'>#{shortname(name)}</a>"
|
||||||
end)
|
|
||||||
|
_ ->
|
||||||
|
""
|
||||||
|
end)
|
||||||
|
|
||||||
Enum.join([text | attachment_text], "<br>")
|
Enum.join([text | attachment_text], "<br>")
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_input(text, mentions, tags) do
|
def format_input(text, mentions, tags) do
|
||||||
text
|
text
|
||||||
|> Formatter.html_escape
|
|> Formatter.html_escape()
|
||||||
|> String.replace("\n", "<br>")
|
|> String.replace("\n", "<br>")
|
||||||
|> (&({[], &1})).()
|
|> (&{[], &1}).()
|
||||||
|> Formatter.add_links
|
|> Formatter.add_links()
|
||||||
|> Formatter.add_user_links(mentions)
|
|> Formatter.add_user_links(mentions)
|
||||||
|> Formatter.add_hashtag_links(tags)
|
|> Formatter.add_hashtag_links(tags)
|
||||||
|> Formatter.finalize
|
|> Formatter.finalize()
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_tag_links(text, tags) do
|
def add_tag_links(text, tags) do
|
||||||
tags = tags
|
tags =
|
||||||
|> Enum.sort_by(fn ({tag, _}) -> -String.length(tag) end)
|
tags
|
||||||
|
|> Enum.sort_by(fn {tag, _} -> -String.length(tag) end)
|
||||||
|
|
||||||
Enum.reduce(tags, text, fn({full, tag}, text) ->
|
Enum.reduce(tags, text, fn {full, tag}, text ->
|
||||||
url = "#<a href='#{Pleroma.Web.base_url}/tag/#{tag}' rel='tag'>#{tag}</a>"
|
url = "#<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>#{tag}</a>"
|
||||||
String.replace(text, full, url)
|
String.replace(text, full, url)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_note_data(actor, to, context, content_html, attachments, inReplyTo, tags, cw \\ nil, cc \\ []) do
|
def make_note_data(
|
||||||
object = %{
|
actor,
|
||||||
"type" => "Note",
|
to,
|
||||||
"to" => to,
|
context,
|
||||||
"cc" => cc,
|
content_html,
|
||||||
"content" => content_html,
|
attachments,
|
||||||
"summary" => cw,
|
inReplyTo,
|
||||||
"context" => context,
|
tags,
|
||||||
"attachment" => attachments,
|
cw \\ nil,
|
||||||
"actor" => actor,
|
cc \\ []
|
||||||
"tag" => tags |> Enum.map(fn ({_, tag}) -> tag end)
|
) 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
|
if inReplyTo do
|
||||||
object
|
object
|
||||||
|
@ -130,24 +151,25 @@ def format_asctime(date) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def date_to_asctime(date) do
|
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)
|
format_asctime(date)
|
||||||
else _e ->
|
else
|
||||||
|
_e ->
|
||||||
""
|
""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_masto_date(%NaiveDateTime{} = date) do
|
def to_masto_date(%NaiveDateTime{} = date) do
|
||||||
date
|
date
|
||||||
|> NaiveDateTime.to_iso8601
|
|> NaiveDateTime.to_iso8601()
|
||||||
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
|
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_masto_date(date) do
|
def to_masto_date(date) do
|
||||||
try do
|
try do
|
||||||
date
|
date
|
||||||
|> NaiveDateTime.from_iso8601!
|
|> NaiveDateTime.from_iso8601!()
|
||||||
|> NaiveDateTime.to_iso8601
|
|> NaiveDateTime.to_iso8601()
|
||||||
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
|
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
|
||||||
rescue
|
rescue
|
||||||
_e -> ""
|
_e -> ""
|
||||||
|
|
|
@ -2,47 +2,55 @@ defmodule Pleroma.Web.Endpoint do
|
||||||
use Phoenix.Endpoint, otp_app: :pleroma
|
use Phoenix.Endpoint, otp_app: :pleroma
|
||||||
|
|
||||||
if Application.get_env(:pleroma, :chat) |> Keyword.get(:enabled) do
|
if Application.get_env(:pleroma, :chat) |> Keyword.get(:enabled) do
|
||||||
socket "/socket", Pleroma.Web.UserSocket
|
socket("/socket", Pleroma.Web.UserSocket)
|
||||||
end
|
end
|
||||||
socket "/api/v1", Pleroma.Web.MastodonAPI.MastodonSocket
|
|
||||||
|
socket("/api/v1", Pleroma.Web.MastodonAPI.MastodonSocket)
|
||||||
|
|
||||||
# Serve at "/" the static files from "priv/static" directory.
|
# Serve at "/" the static files from "priv/static" directory.
|
||||||
#
|
#
|
||||||
# You should set gzip to true if you are running phoenix.digest
|
# You should set gzip to true if you are running phoenix.digest
|
||||||
# when deploying your static files in production.
|
# when deploying your static files in production.
|
||||||
plug Plug.Static,
|
plug(Plug.Static, at: "/media", from: "uploads", gzip: false)
|
||||||
at: "/media", from: "uploads", gzip: false
|
|
||||||
plug Plug.Static,
|
plug(
|
||||||
at: "/", from: :pleroma,
|
Plug.Static,
|
||||||
|
at: "/",
|
||||||
|
from: :pleroma,
|
||||||
only: ~w(index.html static finmoji emoji packs sounds images instance sw.js)
|
only: ~w(index.html static finmoji emoji packs sounds images instance sw.js)
|
||||||
|
)
|
||||||
|
|
||||||
# Code reloading can be explicitly enabled under the
|
# Code reloading can be explicitly enabled under the
|
||||||
# :code_reloader configuration of your endpoint.
|
# :code_reloader configuration of your endpoint.
|
||||||
if code_reloading? do
|
if code_reloading? do
|
||||||
plug Phoenix.CodeReloader
|
plug(Phoenix.CodeReloader)
|
||||||
end
|
end
|
||||||
|
|
||||||
plug TrailingFormatPlug
|
plug(TrailingFormatPlug)
|
||||||
plug Plug.RequestId
|
plug(Plug.RequestId)
|
||||||
plug Plug.Logger
|
plug(Plug.Logger)
|
||||||
|
|
||||||
plug Plug.Parsers,
|
plug(
|
||||||
|
Plug.Parsers,
|
||||||
parsers: [:urlencoded, :multipart, :json],
|
parsers: [:urlencoded, :multipart, :json],
|
||||||
pass: ["*/*"],
|
pass: ["*/*"],
|
||||||
json_decoder: Jason
|
json_decoder: Jason
|
||||||
|
)
|
||||||
|
|
||||||
plug Plug.MethodOverride
|
plug(Plug.MethodOverride)
|
||||||
plug Plug.Head
|
plug(Plug.Head)
|
||||||
|
|
||||||
# The session will be stored in the cookie and signed,
|
# The session will be stored in the cookie and signed,
|
||||||
# this means its contents can be read but not tampered with.
|
# this means its contents can be read but not tampered with.
|
||||||
# Set :encryption_salt if you would also like to encrypt it.
|
# Set :encryption_salt if you would also like to encrypt it.
|
||||||
plug Plug.Session,
|
plug(
|
||||||
|
Plug.Session,
|
||||||
store: :cookie,
|
store: :cookie,
|
||||||
key: "_pleroma_key",
|
key: "_pleroma_key",
|
||||||
signing_salt: "CqaoopA2"
|
signing_salt: "CqaoopA2"
|
||||||
|
)
|
||||||
|
|
||||||
plug Pleroma.Web.Router
|
plug(Pleroma.Web.Router)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Dynamically loads configuration from the system environment
|
Dynamically loads configuration from the system environment
|
||||||
|
|
|
@ -16,27 +16,36 @@ defmodule Pleroma.Web.Federator do
|
||||||
|
|
||||||
def start_link do
|
def start_link do
|
||||||
spawn(fn ->
|
spawn(fn ->
|
||||||
Process.sleep(1000 * 60 * 1) # 1 minute
|
# 1 minute
|
||||||
|
Process.sleep(1000 * 60 * 1)
|
||||||
enqueue(:refresh_subscriptions, nil)
|
enqueue(:refresh_subscriptions, nil)
|
||||||
end)
|
end)
|
||||||
GenServer.start_link(__MODULE__, %{
|
|
||||||
in: {:sets.new(), []},
|
GenServer.start_link(
|
||||||
out: {:sets.new(), []}
|
__MODULE__,
|
||||||
}, name: __MODULE__)
|
%{
|
||||||
|
in: {:sets.new(), []},
|
||||||
|
out: {:sets.new(), []}
|
||||||
|
},
|
||||||
|
name: __MODULE__
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle(:refresh_subscriptions, _) do
|
def handle(:refresh_subscriptions, _) do
|
||||||
Logger.debug("Federator running refresh subscriptions")
|
Logger.debug("Federator running refresh subscriptions")
|
||||||
Websub.refresh_subscriptions()
|
Websub.refresh_subscriptions()
|
||||||
|
|
||||||
spawn(fn ->
|
spawn(fn ->
|
||||||
Process.sleep(1000 * 60 * 60 * 6) # 6 hours
|
# 6 hours
|
||||||
|
Process.sleep(1000 * 60 * 60 * 6)
|
||||||
enqueue(:refresh_subscriptions, nil)
|
enqueue(:refresh_subscriptions, nil)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle(:request_subscription, websub) do
|
def handle(:request_subscription, websub) do
|
||||||
Logger.debug("Refreshing #{websub.topic}")
|
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}")
|
Logger.debug("Successfully refreshed #{websub.topic}")
|
||||||
else
|
else
|
||||||
_e -> Logger.debug("Couldn't refresh #{websub.topic}")
|
_e -> Logger.debug("Couldn't refresh #{websub.topic}")
|
||||||
|
@ -45,8 +54,10 @@ def handle(:request_subscription, websub) do
|
||||||
|
|
||||||
def handle(:publish, activity) do
|
def handle(:publish, activity) do
|
||||||
Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end)
|
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
|
with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||||
{:ok, actor} = WebFinger.ensure_keys_present(actor)
|
{:ok, actor} = WebFinger.ensure_keys_present(actor)
|
||||||
|
|
||||||
if ActivityPub.is_public?(activity) do
|
if ActivityPub.is_public?(activity) do
|
||||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end)
|
Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end)
|
||||||
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
|
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
|
||||||
|
@ -61,7 +72,10 @@ def handle(:publish, activity) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle(:verify_websub, websub) do
|
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)
|
@websub.verify(websub)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -72,16 +86,18 @@ def handle(:incoming_doc, doc) do
|
||||||
|
|
||||||
def handle(:incoming_ap_doc, params) do
|
def handle(:incoming_ap_doc, params) do
|
||||||
Logger.info("Handling incoming AP activity")
|
Logger.info("Handling incoming AP activity")
|
||||||
|
|
||||||
with {:ok, _user} <- ap_enabled_actor(params["actor"]),
|
with {:ok, _user} <- ap_enabled_actor(params["actor"]),
|
||||||
nil <- Activity.get_by_ap_id(params["id"]),
|
nil <- Activity.get_by_ap_id(params["id"]),
|
||||||
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
|
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
|
||||||
else
|
else
|
||||||
%Activity{} ->
|
%Activity{} ->
|
||||||
Logger.info("Already had #{params["id"]}")
|
Logger.info("Already had #{params["id"]}")
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
# Just drop those for now
|
# Just drop those for now
|
||||||
Logger.info("Unhandled activity")
|
Logger.info("Unhandled activity")
|
||||||
Logger.info(Poison.encode!(params, [pretty: 2]))
|
Logger.info(Poison.encode!(params, pretty: 2))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -93,12 +109,21 @@ def handle(:publish_single_websub, %{xml: xml, topic: topic, callback: callback,
|
||||||
signature = @websub.sign(secret || "", xml)
|
signature = @websub.sign(secret || "", xml)
|
||||||
Logger.debug(fn -> "Pushing #{topic} to #{callback}" end)
|
Logger.debug(fn -> "Pushing #{topic} to #{callback}" end)
|
||||||
|
|
||||||
with {:ok, %{status_code: code}} <- @httpoison.post(callback, xml, [
|
with {:ok, %{status_code: code}} <-
|
||||||
{"Content-Type", "application/atom+xml"},
|
@httpoison.post(
|
||||||
{"X-Hub-Signature", "sha1=#{signature}"}
|
callback,
|
||||||
], timeout: 10000, recv_timeout: 20000, hackney: [pool: :default]) do
|
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)
|
Logger.debug(fn -> "Pushed to #{callback}, code #{code}" end)
|
||||||
else e ->
|
else
|
||||||
|
e ->
|
||||||
Logger.debug(fn -> "Couldn't push to #{callback}, #{inspect(e)}" end)
|
Logger.debug(fn -> "Couldn't push to #{callback}, #{inspect(e)}" end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -110,7 +135,7 @@ def handle(type, _) do
|
||||||
|
|
||||||
def enqueue(type, payload, priority \\ 1) do
|
def enqueue(type, payload, priority \\ 1) do
|
||||||
if @federating do
|
if @federating do
|
||||||
if Mix.env == :test do
|
if Mix.env() == :test do
|
||||||
handle(type, payload)
|
handle(type, payload)
|
||||||
else
|
else
|
||||||
GenServer.cast(__MODULE__, {:enqueue, type, payload, priority})
|
GenServer.cast(__MODULE__, {:enqueue, type, payload, priority})
|
||||||
|
@ -119,7 +144,7 @@ def enqueue(type, payload, priority \\ 1) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_start_job(running_jobs, queue) do
|
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)
|
{{type, payload}, queue} = queue_pop(queue)
|
||||||
{:ok, pid} = Task.start(fn -> handle(type, payload) end)
|
{:ok, pid} = Task.start(fn -> handle(type, payload) end)
|
||||||
mref = Process.monitor(pid)
|
mref = Process.monitor(pid)
|
||||||
|
@ -129,7 +154,8 @@ def maybe_start_job(running_jobs, queue) do
|
||||||
end
|
end
|
||||||
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
|
%{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state
|
||||||
i_queue = enqueue_sorted(i_queue, {type, payload}, 1)
|
i_queue = enqueue_sorted(i_queue, {type, payload}, 1)
|
||||||
{i_running_jobs, i_queue} = maybe_start_job(i_running_jobs, i_queue)
|
{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
|
def enqueue_sorted(queue, element, priority) do
|
||||||
[%{item: element, priority: priority} | queue]
|
[%{item: element, priority: priority} | queue]
|
||||||
|> Enum.sort_by(fn (%{priority: priority}) -> priority end)
|
|> Enum.sort_by(fn %{priority: priority} -> priority end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def queue_pop([%{item: element} | queue]) do
|
def queue_pop([%{item: element} | queue]) do
|
||||||
|
@ -169,6 +195,7 @@ def queue_pop([%{item: element} | queue]) do
|
||||||
|
|
||||||
def ap_enabled_actor(id) do
|
def ap_enabled_actor(id) do
|
||||||
user = User.get_by_ap_id(id)
|
user = User.get_by_ap_id(id)
|
||||||
|
|
||||||
if User.ap_enabled?(user) do
|
if User.ap_enabled?(user) do
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -11,8 +11,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
def create_app(conn, params) do
|
def create_app(conn, params) do
|
||||||
with cs <- App.register_changeset(%App{}, params) |> IO.inspect,
|
with cs <- App.register_changeset(%App{}, params) |> IO.inspect(),
|
||||||
{:ok, app} <- Repo.insert(cs) |> IO.inspect do
|
{:ok, app} <- Repo.insert(cs) |> IO.inspect() do
|
||||||
res = %{
|
res = %{
|
||||||
id: app.id,
|
id: app.id,
|
||||||
client_id: app.client_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
|
def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
||||||
original_user = user
|
original_user = user
|
||||||
params = if bio = params["note"] do
|
|
||||||
Map.put(params, "bio", bio)
|
|
||||||
else
|
|
||||||
params
|
|
||||||
end
|
|
||||||
|
|
||||||
params = if name = params["display_name"] do
|
params =
|
||||||
Map.put(params, "name", name)
|
if bio = params["note"] do
|
||||||
else
|
Map.put(params, "bio", bio)
|
||||||
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
|
else
|
||||||
_e -> user
|
params
|
||||||
end
|
end
|
||||||
else
|
|
||||||
user
|
|
||||||
end
|
|
||||||
|
|
||||||
user = if banner = params["header"] do
|
params =
|
||||||
with %Plug.Upload{} <- banner,
|
if name = params["display_name"] do
|
||||||
{:ok, object} <- ActivityPub.upload(banner),
|
Map.put(params, "name", name)
|
||||||
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
|
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
|
end
|
||||||
else
|
|
||||||
user
|
|
||||||
end
|
|
||||||
|
|
||||||
with changeset <- User.update_changeset(user, params),
|
with changeset <- User.update_changeset(user, params),
|
||||||
{:ok, user} <- User.update_and_set_cache(changeset) do
|
{:ok, user} <- User.update_and_set_cache(changeset) do
|
||||||
if original_user != user do
|
if original_user != user do
|
||||||
CommonAPI.update(user)
|
CommonAPI.update(user)
|
||||||
end
|
end
|
||||||
json conn, AccountView.render("account.json", %{user: user})
|
|
||||||
|
json(conn, AccountView.render("account.json", %{user: user}))
|
||||||
else
|
else
|
||||||
_e ->
|
_e ->
|
||||||
conn
|
conn
|
||||||
|
@ -88,9 +94,10 @@ def user(conn, %{"id" => id}) do
|
||||||
account = AccountView.render("account.json", %{user: user})
|
account = AccountView.render("account.json", %{user: user})
|
||||||
json(conn, account)
|
json(conn, account)
|
||||||
else
|
else
|
||||||
_e -> conn
|
_e ->
|
||||||
|> put_status(404)
|
conn
|
||||||
|> json(%{error: "Can't find user"})
|
|> put_status(404)
|
||||||
|
|> json(%{error: "Can't find user"})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -98,16 +105,16 @@ def user(conn, %{"id" => id}) do
|
||||||
|
|
||||||
def masto_instance(conn, _params) do
|
def masto_instance(conn, _params) do
|
||||||
response = %{
|
response = %{
|
||||||
uri: Web.base_url,
|
uri: Web.base_url(),
|
||||||
title: Keyword.get(@instance, :name),
|
title: Keyword.get(@instance, :name),
|
||||||
description: "A Pleroma instance, an alternative fediverse server",
|
description: "A Pleroma instance, an alternative fediverse server",
|
||||||
version: Keyword.get(@instance, :version),
|
version: Keyword.get(@instance, :version),
|
||||||
email: Keyword.get(@instance, :email),
|
email: Keyword.get(@instance, :email),
|
||||||
urls: %{
|
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,
|
stats: Stats.get_stats(),
|
||||||
thumbnail: Web.base_url <> "/instance/thumbnail.jpeg",
|
thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg",
|
||||||
max_toot_chars: Keyword.get(@instance, :limit)
|
max_toot_chars: Keyword.get(@instance, :limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,13 +122,14 @@ def masto_instance(conn, _params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def peers(conn, _params) do
|
def peers(conn, _params) do
|
||||||
json(conn, Stats.get_peers)
|
json(conn, Stats.get_peers())
|
||||||
end
|
end
|
||||||
|
|
||||||
defp mastodonized_emoji do
|
defp mastodonized_emoji do
|
||||||
Pleroma.Formatter.get_custom_emoji()
|
Pleroma.Formatter.get_custom_emoji()
|
||||||
|> Enum.map(fn {shortcode, relative_url} ->
|
|> 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,
|
"shortcode" => shortcode,
|
||||||
"static_url" => url,
|
"static_url" => url,
|
||||||
|
@ -132,26 +140,30 @@ defp mastodonized_emoji do
|
||||||
|
|
||||||
def custom_emojis(conn, _params) do
|
def custom_emojis(conn, _params) do
|
||||||
mastodon_emoji = mastodonized_emoji()
|
mastodon_emoji = mastodonized_emoji()
|
||||||
json conn, mastodon_emoji
|
json(conn, mastodon_emoji)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp add_link_headers(conn, method, activities, param \\ false) do
|
defp add_link_headers(conn, method, activities, param \\ false) do
|
||||||
last = List.last(activities)
|
last = List.last(activities)
|
||||||
first = List.first(activities)
|
first = List.first(activities)
|
||||||
|
|
||||||
if last do
|
if last do
|
||||||
min = last.id
|
min = last.id
|
||||||
max = first.id
|
max = first.id
|
||||||
{next_url, prev_url} = if param do
|
|
||||||
{
|
{next_url, prev_url} =
|
||||||
mastodon_api_url(Pleroma.Web.Endpoint, method, param, max_id: min),
|
if param do
|
||||||
mastodon_api_url(Pleroma.Web.Endpoint, method, param, since_id: max)
|
{
|
||||||
}
|
mastodon_api_url(Pleroma.Web.Endpoint, method, param, max_id: min),
|
||||||
else
|
mastodon_api_url(Pleroma.Web.Endpoint, method, param, since_id: max)
|
||||||
{
|
}
|
||||||
mastodon_api_url(Pleroma.Web.Endpoint, method, max_id: min),
|
else
|
||||||
mastodon_api_url(Pleroma.Web.Endpoint, method, since_id: max)
|
{
|
||||||
}
|
mastodon_api_url(Pleroma.Web.Endpoint, method, max_id: min),
|
||||||
end
|
mastodon_api_url(Pleroma.Web.Endpoint, method, since_id: max)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_header("link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
|
|> put_resp_header("link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
|
||||||
else
|
else
|
||||||
|
@ -160,13 +172,15 @@ defp add_link_headers(conn, method, activities, param \\ false) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def home_timeline(%{assigns: %{user: user}} = conn, params) do
|
def home_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
params = params
|
params =
|
||||||
|> Map.put("type", ["Create", "Announce"])
|
params
|
||||||
|> Map.put("blocking_user", user)
|
|> Map.put("type", ["Create", "Announce"])
|
||||||
|> Map.put("user", user)
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("user", user)
|
||||||
|
|
||||||
activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
activities =
|
||||||
|> Enum.reverse
|
ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(:home_timeline, activities)
|
|> add_link_headers(:home_timeline, activities)
|
||||||
|
@ -174,13 +188,15 @@ def home_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def public_timeline(%{assigns: %{user: user}} = conn, params) do
|
def public_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
params = params
|
params =
|
||||||
|> Map.put("type", ["Create", "Announce"])
|
params
|
||||||
|> Map.put("local_only", params["local"] in [true, "True", "true", "1"])
|
|> Map.put("type", ["Create", "Announce"])
|
||||||
|> Map.put("blocking_user", user)
|
|> Map.put("local_only", params["local"] in [true, "True", "true", "1"])
|
||||||
|
|> Map.put("blocking_user", user)
|
||||||
|
|
||||||
activities = ActivityPub.fetch_public_activities(params)
|
activities =
|
||||||
|> Enum.reverse
|
ActivityPub.fetch_public_activities(params)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(:public_timeline, activities)
|
|> 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
|
def user_statuses(%{assigns: %{user: user}} = conn, params) do
|
||||||
with %User{ap_id: ap_id} <- Repo.get(User, params["id"]) do
|
with %User{ap_id: ap_id} <- Repo.get(User, params["id"]) do
|
||||||
params = params
|
params =
|
||||||
|> Map.put("type", ["Create", "Announce"])
|
params
|
||||||
|> Map.put("actor_id", ap_id)
|
|> Map.put("type", ["Create", "Announce"])
|
||||||
|> Map.put("whole_db", true)
|
|> Map.put("actor_id", ap_id)
|
||||||
|
|> Map.put("whole_db", true)
|
||||||
|
|
||||||
activities = ActivityPub.fetch_public_activities(params)
|
activities =
|
||||||
|> Enum.reverse
|
ActivityPub.fetch_public_activities(params)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(:user_statuses, activities, params["id"])
|
|> 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
|
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||||
true <- ActivityPub.visible_for_user?(activity, user) do
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||||
activities <- ActivityPub.fetch_activities_for_context(activity.data["context"], %{"blocking_user" => user, "user" => user}),
|
activities <-
|
||||||
activities <- activities |> Enum.filter(fn (%{id: aid}) -> to_string(aid) != to_string(id) end),
|
ActivityPub.fetch_activities_for_context(activity.data["context"], %{
|
||||||
activities <- activities |> Enum.filter(fn (%{data: %{"type" => type}}) -> type == "Create" end),
|
"blocking_user" => user,
|
||||||
grouped_activities <- Enum.group_by(activities, fn (%{id: id}) -> id < activity.id end) do
|
"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 = %{
|
result = %{
|
||||||
ancestors: StatusView.render("index.json", for: user, activities: grouped_activities[true] || [], as: :activity) |> Enum.reverse,
|
ancestors:
|
||||||
descendants: StatusView.render("index.json", for: user, activities: grouped_activities[false] || [], as: :activity) |> Enum.reverse,
|
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)
|
json(conn, result)
|
||||||
|
@ -226,12 +264,13 @@ def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
|
def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
|
||||||
params = params
|
params =
|
||||||
|> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
|
params
|
||||||
|> Map.put("no_attachment_links", true)
|
|> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
|
||||||
|
|> Map.put("no_attachment_links", true)
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, params)
|
{: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
|
end
|
||||||
|
|
||||||
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
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
|
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
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
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),
|
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
|
%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
|
||||||
end
|
end
|
||||||
|
|
||||||
def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
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),
|
with {:ok, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user),
|
||||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
%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
|
||||||
end
|
end
|
||||||
|
|
||||||
def notifications(%{assigns: %{user: user}} = conn, params) do
|
def notifications(%{assigns: %{user: user}} = conn, params) do
|
||||||
notifications = Notification.for_user(user, params)
|
notifications = Notification.for_user(user, params)
|
||||||
result = Enum.map(notifications, fn x ->
|
|
||||||
render_notification(user, x)
|
result =
|
||||||
end)
|
Enum.map(notifications, fn x ->
|
||||||
|> Enum.filter(&(&1))
|
render_notification(user, x)
|
||||||
|
end)
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(:notifications, notifications)
|
|> 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
|
def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
id = List.wrap(id)
|
id = List.wrap(id)
|
||||||
q = from u in User,
|
q = from(u in User, where: u.id in ^id)
|
||||||
where: u.id in ^id
|
|
||||||
targets = Repo.all(q)
|
targets = Repo.all(q)
|
||||||
render conn, AccountView, "relationships.json", %{user: user, targets: targets}
|
render(conn, AccountView, "relationships.json", %{user: user, targets: targets})
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload(%{assigns: %{user: _}} = conn, %{"file" => file}) do
|
def upload(%{assigns: %{user: _}} = conn, %{"file" => file}) do
|
||||||
with {:ok, object} <- ActivityPub.upload(file) do
|
with {:ok, object} <- ActivityPub.upload(file) do
|
||||||
data = object.data
|
data =
|
||||||
|> Map.put("id", object.id)
|
object.data
|
||||||
|
|> Map.put("id", object.id)
|
||||||
|
|
||||||
render conn, StatusView, "attachment.json", %{attachment: data}
|
render(conn, StatusView, "attachment.json", %{attachment: data})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def favourited_by(conn, %{"id" => id}) do
|
def favourited_by(conn, %{"id" => id}) do
|
||||||
with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do
|
with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do
|
||||||
q = from u in User,
|
q = from(u in User, where: u.ap_id in ^likes)
|
||||||
where: u.ap_id in ^likes
|
|
||||||
users = Repo.all(q)
|
users = Repo.all(q)
|
||||||
render conn, AccountView, "accounts.json", %{users: users, as: :user}
|
render(conn, AccountView, "accounts.json", %{users: users, as: :user})
|
||||||
else
|
else
|
||||||
_ -> json(conn, [])
|
_ -> json(conn, [])
|
||||||
end
|
end
|
||||||
|
@ -334,23 +374,24 @@ def favourited_by(conn, %{"id" => id}) do
|
||||||
|
|
||||||
def reblogged_by(conn, %{"id" => id}) do
|
def reblogged_by(conn, %{"id" => id}) do
|
||||||
with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Repo.get(Activity, id) do
|
with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Repo.get(Activity, id) do
|
||||||
q = from u in User,
|
q = from(u in User, where: u.ap_id in ^announces)
|
||||||
where: u.ap_id in ^announces
|
|
||||||
users = Repo.all(q)
|
users = Repo.all(q)
|
||||||
render conn, AccountView, "accounts.json", %{users: users, as: :user}
|
render(conn, AccountView, "accounts.json", %{users: users, as: :user})
|
||||||
else
|
else
|
||||||
_ -> json(conn, [])
|
_ -> json(conn, [])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
|
def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
params = params
|
params =
|
||||||
|> Map.put("type", "Create")
|
params
|
||||||
|> Map.put("local_only", !!params["local"])
|
|> Map.put("type", "Create")
|
||||||
|> Map.put("blocking_user", user)
|
|> Map.put("local_only", !!params["local"])
|
||||||
|
|> Map.put("blocking_user", user)
|
||||||
|
|
||||||
activities = ActivityPub.fetch_public_activities(params)
|
activities =
|
||||||
|> Enum.reverse
|
ActivityPub.fetch_public_activities(params)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(:hashtag_timeline, activities, params["tag"])
|
|> 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
|
def followers(conn, %{"id" => id}) do
|
||||||
with %User{} = user <- Repo.get(User, id),
|
with %User{} = user <- Repo.get(User, id),
|
||||||
{:ok, followers} <- User.get_followers(user) do
|
{: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
|
||||||
end
|
end
|
||||||
|
|
||||||
def following(conn, %{"id" => id}) do
|
def following(conn, %{"id" => id}) do
|
||||||
with %User{} = user <- Repo.get(User, id),
|
with %User{} = user <- Repo.get(User, id),
|
||||||
{:ok, followers} <- User.get_friends(user) do
|
{: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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -376,7 +417,7 @@ def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||||
with %User{} = followed <- Repo.get(User, id),
|
with %User{} = followed <- Repo.get(User, id),
|
||||||
{:ok, follower} <- User.follow(follower, followed),
|
{:ok, follower} <- User.follow(follower, followed),
|
||||||
{:ok, _activity} <- ActivityPub.follow(follower, followed) do
|
{: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
|
else
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|
@ -389,7 +430,7 @@ def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
|
||||||
with %User{} = followed <- Repo.get_by(User, nickname: uri),
|
with %User{} = followed <- Repo.get_by(User, nickname: uri),
|
||||||
{:ok, follower} <- User.follow(follower, followed),
|
{:ok, follower} <- User.follow(follower, followed),
|
||||||
{:ok, _activity} <- ActivityPub.follow(follower, followed) do
|
{:ok, _activity} <- ActivityPub.follow(follower, followed) do
|
||||||
render conn, AccountView, "account.json", %{user: followed}
|
render(conn, AccountView, "account.json", %{user: followed})
|
||||||
else
|
else
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|
@ -401,20 +442,22 @@ def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
|
||||||
# TODO: Clean up and unify
|
# TODO: Clean up and unify
|
||||||
def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||||
with %User{} = followed <- Repo.get(User, id),
|
with %User{} = followed <- Repo.get(User, id),
|
||||||
{ :ok, follower, follow_activity } <- User.unfollow(follower, followed),
|
{:ok, follower, follow_activity} <- User.unfollow(follower, followed),
|
||||||
{ :ok, _activity } <- ActivityPub.insert(%{
|
{:ok, _activity} <-
|
||||||
"type" => "Undo",
|
ActivityPub.insert(%{
|
||||||
"actor" => follower.ap_id,
|
"type" => "Undo",
|
||||||
"object" => follow_activity.data["id"] # get latest Follow for these users
|
"actor" => follower.ap_id,
|
||||||
}) do
|
# get latest Follow for these users
|
||||||
render conn, AccountView, "relationship.json", %{user: follower, target: followed}
|
"object" => follow_activity.data["id"]
|
||||||
|
}) do
|
||||||
|
render(conn, AccountView, "relationship.json", %{user: follower, target: followed})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||||
with %User{} = blocked <- Repo.get(User, id),
|
with %User{} = blocked <- Repo.get(User, id),
|
||||||
{:ok, blocker} <- User.block(blocker, blocked) do
|
{: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
|
else
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|
@ -426,7 +469,7 @@ def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||||
def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||||
with %User{} = blocked <- Repo.get(User, id),
|
with %User{} = blocked <- Repo.get(User, id),
|
||||||
{:ok, blocker} <- User.unblock(blocker, blocked) do
|
{: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
|
else
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|
@ -438,7 +481,7 @@ def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||||
# TODO: Use proper query
|
# TODO: Use proper query
|
||||||
def blocks(%{assigns: %{user: user}} = conn, _) do
|
def blocks(%{assigns: %{user: user}} = conn, _) do
|
||||||
with blocked_users <- user.info["blocks"] || [],
|
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)
|
res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
|
||||||
json(conn, res)
|
json(conn, res)
|
||||||
end
|
end
|
||||||
|
@ -447,23 +490,34 @@ def blocks(%{assigns: %{user: user}} = conn, _) do
|
||||||
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||||
accounts = User.search(query, params["resolve"] == "true")
|
accounts = User.search(query, params["resolve"] == "true")
|
||||||
|
|
||||||
fetched = if Regex.match?(~r/https?:/, query) do
|
fetched =
|
||||||
with {:ok, activities} <- OStatus.fetch_activity_from_url(query) do
|
if Regex.match?(~r/https?:/, query) do
|
||||||
activities
|
with {:ok, activities} <- OStatus.fetch_activity_from_url(query) do
|
||||||
else
|
activities
|
||||||
_e -> []
|
else
|
||||||
end
|
_e -> []
|
||||||
end || []
|
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
|
statuses = Repo.all(q) ++ fetched
|
||||||
|
|
||||||
res = %{
|
res = %{
|
||||||
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
|
"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" => []
|
"hashtags" => []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -479,94 +533,102 @@ def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) d
|
||||||
end
|
end
|
||||||
|
|
||||||
def favourites(%{assigns: %{user: user}} = conn, _) do
|
def favourites(%{assigns: %{user: user}} = conn, _) do
|
||||||
params = %{}
|
params =
|
||||||
|> Map.put("type", "Create")
|
%{}
|
||||||
|> Map.put("favorited_by", user.ap_id)
|
|> Map.put("type", "Create")
|
||||||
|> Map.put("blocking_user", user)
|
|> Map.put("favorited_by", user.ap_id)
|
||||||
|
|> Map.put("blocking_user", user)
|
||||||
|
|
||||||
activities = ActivityPub.fetch_public_activities(params)
|
activities =
|
||||||
|> Enum.reverse
|
ActivityPub.fetch_public_activities(params)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
|
|> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
|
||||||
end
|
end
|
||||||
|
|
||||||
def index(%{assigns: %{user: user}} = conn, _params) do
|
def index(%{assigns: %{user: user}} = conn, _params) do
|
||||||
token = conn
|
token =
|
||||||
|> get_session(:oauth_token)
|
conn
|
||||||
|
|> get_session(:oauth_token)
|
||||||
|
|
||||||
if user && token do
|
if user && token do
|
||||||
mastodon_emoji = mastodonized_emoji()
|
mastodon_emoji = mastodonized_emoji()
|
||||||
accounts = Map.put(%{}, user.id, AccountView.render("account.json", %{user: user}))
|
accounts = Map.put(%{}, user.id, AccountView.render("account.json", %{user: user}))
|
||||||
initial_state = %{
|
|
||||||
meta: %{
|
initial_state =
|
||||||
streaming_api_base_url: String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
|
%{
|
||||||
access_token: token,
|
meta: %{
|
||||||
locale: "en",
|
streaming_api_base_url:
|
||||||
domain: Pleroma.Web.Endpoint.host(),
|
String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
|
||||||
admin: "1",
|
access_token: token,
|
||||||
me: "#{user.id}",
|
locale: "en",
|
||||||
unfollow_modal: false,
|
domain: Pleroma.Web.Endpoint.host(),
|
||||||
boost_modal: false,
|
admin: "1",
|
||||||
delete_modal: true,
|
me: "#{user.id}",
|
||||||
auto_play_gif: false,
|
unfollow_modal: false,
|
||||||
reduce_motion: false
|
boost_modal: false,
|
||||||
},
|
delete_modal: true,
|
||||||
compose: %{
|
auto_play_gif: false,
|
||||||
me: "#{user.id}",
|
reduce_motion: false
|
||||||
default_privacy: "public",
|
},
|
||||||
default_sensitive: false
|
compose: %{
|
||||||
},
|
me: "#{user.id}",
|
||||||
media_attachments: %{
|
default_privacy: "public",
|
||||||
accept_content_types: [
|
default_sensitive: false
|
||||||
".jpg",
|
},
|
||||||
".jpeg",
|
media_attachments: %{
|
||||||
".png",
|
accept_content_types: [
|
||||||
".gif",
|
".jpg",
|
||||||
".webm",
|
".jpeg",
|
||||||
".mp4",
|
".png",
|
||||||
".m4v",
|
".gif",
|
||||||
"image\/jpeg",
|
".webm",
|
||||||
"image\/png",
|
".mp4",
|
||||||
"image\/gif",
|
".m4v",
|
||||||
"video\/webm",
|
"image\/jpeg",
|
||||||
"video\/mp4"
|
"image\/png",
|
||||||
]
|
"image\/gif",
|
||||||
},
|
"video\/webm",
|
||||||
settings: %{
|
"video\/mp4"
|
||||||
onboarded: true,
|
]
|
||||||
home: %{
|
},
|
||||||
shows: %{
|
settings: %{
|
||||||
reblog: true,
|
onboarded: true,
|
||||||
reply: 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: %{
|
push_subscription: nil,
|
||||||
alerts: %{
|
accounts: accounts,
|
||||||
follow: true,
|
custom_emojis: mastodon_emoji,
|
||||||
favourite: true,
|
char_limit: Keyword.get(@instance, :limit)
|
||||||
reblog: true,
|
}
|
||||||
mention: true
|
|> Jason.encode!()
|
||||||
},
|
|
||||||
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!
|
|
||||||
conn
|
conn
|
||||||
|> put_layout(false)
|
|> put_layout(false)
|
||||||
|> render(MastodonView, "index.html", %{initial_state: initial_state})
|
|> render(MastodonView, "index.html", %{initial_state: initial_state})
|
||||||
|
@ -586,12 +648,18 @@ defp get_or_make_app() do
|
||||||
{:ok, app}
|
{:ok, app}
|
||||||
else
|
else
|
||||||
_e ->
|
_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)
|
Repo.insert(cs)
|
||||||
end
|
end
|
||||||
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),
|
with %User{} = user <- User.get_cached_by_nickname(name),
|
||||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||||
{:ok, app} <- get_or_make_app(),
|
{:ok, app} <- get_or_make_app(),
|
||||||
|
@ -615,8 +683,9 @@ def logout(conn, _) do
|
||||||
|
|
||||||
def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
Logger.debug("Unimplemented, returning unmodified relationship")
|
Logger.debug("Unimplemented, returning unmodified relationship")
|
||||||
|
|
||||||
with %User{} = target <- Repo.get(User, id) do
|
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
|
||||||
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
|
def render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do
|
||||||
actor = User.get_cached_by_ap_id(activity.data["actor"])
|
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
|
case activity.data["type"] do
|
||||||
"Create" ->
|
"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" ->
|
"Like" ->
|
||||||
liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
|
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" ->
|
"Announce" ->
|
||||||
announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
|
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" ->
|
"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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,17 +4,23 @@ defmodule Pleroma.Web.MastodonAPI.MastodonSocket do
|
||||||
alias Pleroma.Web.OAuth.Token
|
alias Pleroma.Web.OAuth.Token
|
||||||
alias Pleroma.{User, Repo}
|
alias Pleroma.{User, Repo}
|
||||||
|
|
||||||
transport :streaming, Phoenix.Transports.WebSocket.Raw,
|
transport(
|
||||||
timeout: :infinity # We never receive data.
|
:streaming,
|
||||||
|
Phoenix.Transports.WebSocket.Raw,
|
||||||
|
# We never receive data.
|
||||||
|
timeout: :infinity
|
||||||
|
)
|
||||||
|
|
||||||
def connect(params, socket) do
|
def connect(params, socket) do
|
||||||
with token when not is_nil(token) <- params["access_token"],
|
with token when not is_nil(token) <- params["access_token"],
|
||||||
%Token{user_id: user_id} <- Repo.get_by(Token, token: token),
|
%Token{user_id: user_id} <- Repo.get_by(Token, token: token),
|
||||||
%User{} = user <- Repo.get(User, user_id),
|
%User{} = user <- Repo.get(User, user_id),
|
||||||
stream when stream in ["public", "public:local", "user"] <- params["stream"] do
|
stream when stream in ["public", "public:local", "user"] <- params["stream"] do
|
||||||
socket = socket
|
socket =
|
||||||
|> assign(:topic, params["stream"])
|
socket
|
||||||
|> assign(:user, user)
|
|> assign(:topic, params["stream"])
|
||||||
|
|> assign(:user, user)
|
||||||
|
|
||||||
Pleroma.Web.Streamer.add_socket(params["stream"], socket)
|
Pleroma.Web.Streamer.add_socket(params["stream"], socket)
|
||||||
{:ok, socket}
|
{:ok, socket}
|
||||||
else
|
else
|
||||||
|
@ -25,11 +31,11 @@ def connect(params, socket) do
|
||||||
def id(_), do: nil
|
def id(_), do: nil
|
||||||
|
|
||||||
def handle(:text, message, _state) do
|
def handle(:text, message, _state) do
|
||||||
#| :ok
|
# | :ok
|
||||||
#| state
|
# | state
|
||||||
#| {:text, message}
|
# | {:text, message}
|
||||||
#| {:text, message, state}
|
# | {:text, message, state}
|
||||||
#| {:close, "Goodbye!"}
|
# | {:close, "Goodbye!"}
|
||||||
{:text, message}
|
{:text, message}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -10,37 +10,52 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||||
defp get_replied_to_activities(activities) do
|
defp get_replied_to_activities(activities) do
|
||||||
activities
|
activities
|
||||||
|> Enum.map(fn
|
|> Enum.map(fn
|
||||||
(%{data: %{"type" => "Create", "object" => %{"inReplyTo" => inReplyTo}}}) ->
|
%{data: %{"type" => "Create", "object" => %{"inReplyTo" => inReplyTo}}} ->
|
||||||
(inReplyTo != "") && inReplyTo
|
inReplyTo != "" && inReplyTo
|
||||||
_ -> nil
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
end)
|
end)
|
||||||
|> Enum.filter(&(&1))
|
|> Enum.filter(& &1)
|
||||||
|> Activity.create_activity_by_object_id_query()
|
|> Activity.create_activity_by_object_id_query()
|
||||||
|> Repo.all
|
|> Repo.all()
|
||||||
|> Enum.reduce(%{}, fn(activity, acc) -> Map.put(acc,activity.data["object"]["id"], activity) end)
|
|> Enum.reduce(%{}, fn activity, acc ->
|
||||||
|
Map.put(acc, activity.data["object"]["id"], activity)
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("index.json", opts) do
|
def render("index.json", opts) do
|
||||||
replied_to_activities = get_replied_to_activities(opts.activities)
|
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
|
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"])
|
user = User.get_cached_by_ap_id(activity.data["actor"])
|
||||||
created_at = Utils.to_masto_date(activity.data["published"])
|
created_at = Utils.to_masto_date(activity.data["published"])
|
||||||
|
|
||||||
reblogged = Activity.get_create_activity_by_object_ap_id(object)
|
reblogged = Activity.get_create_activity_by_object_ap_id(object)
|
||||||
reblogged = render("status.json", Map.put(opts, :activity, reblogged))
|
reblogged = render("status.json", Map.put(opts, :activity, reblogged))
|
||||||
|
|
||||||
mentions = activity.recipients
|
mentions =
|
||||||
|> Enum.map(fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end)
|
activity.recipients
|
||||||
|> Enum.filter(&(&1))
|
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|
||||||
|> Enum.map(fn (user) -> AccountView.render("mention.json", %{user: user}) end)
|
|> Enum.filter(& &1)
|
||||||
|
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
id: to_string(activity.id),
|
id: to_string(activity.id),
|
||||||
uri: object,
|
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}),
|
account: AccountView.render("account.json", %{user: user}),
|
||||||
in_reply_to_id: nil,
|
in_reply_to_id: nil,
|
||||||
in_reply_to_account_id: nil,
|
in_reply_to_account_id: nil,
|
||||||
|
@ -89,27 +104,30 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
||||||
tags = object["tag"] || []
|
tags = object["tag"] || []
|
||||||
sensitive = object["sensitive"] || Enum.member?(tags, "nsfw")
|
sensitive = object["sensitive"] || Enum.member?(tags, "nsfw")
|
||||||
|
|
||||||
mentions = activity.recipients
|
mentions =
|
||||||
|> Enum.map(fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end)
|
activity.recipients
|
||||||
|> Enum.filter(&(&1))
|
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|
||||||
|> Enum.map(fn (user) -> AccountView.render("mention.json", %{user: user}) 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"] || [])
|
repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || [])
|
||||||
favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
|
favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
|
||||||
|
|
||||||
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"])
|
created_at = Utils.to_masto_date(object["published"])
|
||||||
|
|
||||||
reply_to = get_reply_to(activity, opts)
|
reply_to = get_reply_to(activity, opts)
|
||||||
reply_to_user = reply_to && User.get_cached_by_ap_id(reply_to.data["actor"])
|
reply_to_user = reply_to && User.get_cached_by_ap_id(reply_to.data["actor"])
|
||||||
|
|
||||||
emojis = (activity.data["object"]["emoji"] || [])
|
emojis =
|
||||||
|> Enum.map(fn {name, url} ->
|
(activity.data["object"]["emoji"] || [])
|
||||||
name = HtmlSanitizeEx.strip_tags(name)
|
|> Enum.map(fn {name, url} ->
|
||||||
url = HtmlSanitizeEx.strip_tags(url)
|
name = HtmlSanitizeEx.strip_tags(name)
|
||||||
%{ shortcode: name, url: url, static_url: url }
|
url = HtmlSanitizeEx.strip_tags(url)
|
||||||
end)
|
%{shortcode: name, url: url, static_url: url}
|
||||||
|
end)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
id: to_string(activity.id),
|
id: to_string(activity.id),
|
||||||
|
@ -131,7 +149,8 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
||||||
visibility: get_visibility(object),
|
visibility: get_visibility(object),
|
||||||
media_attachments: attachments |> Enum.take(4),
|
media_attachments: attachments |> Enum.take(4),
|
||||||
mentions: mentions,
|
mentions: mentions,
|
||||||
tags: [], # fix,
|
# fix,
|
||||||
|
tags: [],
|
||||||
application: %{
|
application: %{
|
||||||
name: "Web",
|
name: "Web",
|
||||||
website: nil
|
website: nil
|
||||||
|
@ -145,10 +164,11 @@ def get_visibility(object) do
|
||||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||||
to = object["to"] || []
|
to = object["to"] || []
|
||||||
cc = object["cc"] || []
|
cc = object["cc"] || []
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
public in to -> "public"
|
public in to -> "public"
|
||||||
public in cc -> "unlisted"
|
public in cc -> "unlisted"
|
||||||
Enum.any?(to, &(String.contains?(&1, "/followers"))) -> "private"
|
Enum.any?(to, &String.contains?(&1, "/followers")) -> "private"
|
||||||
true -> "direct"
|
true -> "direct"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -156,14 +176,15 @@ def get_visibility(object) do
|
||||||
def render("attachment.json", %{attachment: attachment}) do
|
def render("attachment.json", %{attachment: attachment}) do
|
||||||
[%{"mediaType" => media_type, "href" => href} | _] = attachment["url"]
|
[%{"mediaType" => media_type, "href" => href} | _] = attachment["url"]
|
||||||
|
|
||||||
type = cond do
|
type =
|
||||||
String.contains?(media_type, "image") -> "image"
|
cond do
|
||||||
String.contains?(media_type, "video") -> "video"
|
String.contains?(media_type, "image") -> "image"
|
||||||
String.contains?(media_type, "audio") -> "audio"
|
String.contains?(media_type, "video") -> "video"
|
||||||
true -> "unknown"
|
String.contains?(media_type, "audio") -> "audio"
|
||||||
end
|
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),
|
id: to_string(attachment["id"] || hash_id),
|
||||||
|
|
|
@ -4,47 +4,59 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
|
||||||
|
|
||||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||||
|
|
||||||
@max_body_length 25 * 1048576
|
@max_body_length 25 * 1_048_576
|
||||||
|
|
||||||
@cache_control %{
|
@cache_control %{
|
||||||
default: "public, max-age=1209600",
|
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
|
def remote(conn, %{"sig" => sig, "url" => url}) do
|
||||||
config = Application.get_env(:pleroma, :media_proxy, [])
|
config = Application.get_env(:pleroma, :media_proxy, [])
|
||||||
with \
|
|
||||||
true <- Keyword.get(config, :enabled, false),
|
with true <- Keyword.get(config, :enabled, false),
|
||||||
{:ok, url} <- Pleroma.Web.MediaProxy.decode_url(sig, url),
|
{:ok, url} <- Pleroma.Web.MediaProxy.decode_url(sig, url),
|
||||||
{:ok, content_type, body} <- proxy_request(url)
|
{:ok, content_type, body} <- proxy_request(url) do
|
||||||
do
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type(content_type)
|
|> put_resp_content_type(content_type)
|
||||||
|> set_cache_header(:default)
|
|> set_cache_header(:default)
|
||||||
|> send_resp(200, body)
|
|> send_resp(200, body)
|
||||||
else
|
else
|
||||||
false -> send_error(conn, 404)
|
false ->
|
||||||
{:error, :invalid_signature} -> send_error(conn, 403)
|
send_error(conn, 404)
|
||||||
{:error, {:http, _, url}} -> redirect_or_error(conn, url, Keyword.get(config, :redirect_on_failure, true))
|
|
||||||
|
{:error, :invalid_signature} ->
|
||||||
|
send_error(conn, 403)
|
||||||
|
|
||||||
|
{:error, {:http, _, url}} ->
|
||||||
|
redirect_or_error(conn, url, Keyword.get(config, :redirect_on_failure, true))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp proxy_request(link) do
|
defp proxy_request(link) do
|
||||||
headers = [{"user-agent", "Pleroma/MediaProxy; #{Pleroma.Web.base_url()} <#{Application.get_env(:pleroma, :instance)[:email]}>"}]
|
headers = [
|
||||||
options = @httpoison.process_request_options([:insecure, {:follow_redirect, true}]) ++ [{:pool, :default}]
|
{"user-agent",
|
||||||
with \
|
"Pleroma/MediaProxy; #{Pleroma.Web.base_url()} <#{
|
||||||
{:ok, 200, headers, client} <- :hackney.request(:get, link, headers, "", options),
|
Application.get_env(:pleroma, :instance)[:email]
|
||||||
headers = Enum.into(headers, Map.new),
|
}>"}
|
||||||
{:ok, body} <- proxy_request_body(client),
|
]
|
||||||
content_type <- proxy_request_content_type(headers, body)
|
|
||||||
do
|
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}
|
{:ok, content_type, body}
|
||||||
else
|
else
|
||||||
{:ok, status, _, _} ->
|
{: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, {:http, :bad_status, link}}
|
||||||
|
|
||||||
{:error, error} ->
|
{: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}}
|
{:error, {:http, error, link}}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -63,13 +75,15 @@ defp send_error(conn, code, body \\ "") do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp proxy_request_body(client), do: proxy_request_body(client, <<>>)
|
defp proxy_request_body(client), do: proxy_request_body(client, <<>>)
|
||||||
|
|
||||||
defp proxy_request_body(client, body) when byte_size(body) < @max_body_length do
|
defp proxy_request_body(client, body) when byte_size(body) < @max_body_length do
|
||||||
case :hackney.stream_body(client) 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}
|
:done -> {:ok, body}
|
||||||
{:error, reason} -> {:error, reason}
|
{:error, reason} -> {:error, reason}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp proxy_request_body(client, _) do
|
defp proxy_request_body(client, _) do
|
||||||
:hackney.close(client)
|
:hackney.close(client)
|
||||||
{:error, :body_too_large}
|
{:error, :body_too_large}
|
||||||
|
@ -80,5 +94,4 @@ defp proxy_request_body(client, _) do
|
||||||
defp proxy_request_content_type(headers, _body) do
|
defp proxy_request_content_type(headers, _body) do
|
||||||
headers["Content-Type"] || headers["content-type"] || "image/jpeg"
|
headers["Content-Type"] || headers["content-type"] || "image/jpeg"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,14 +7,15 @@ def url(url = "/" <> _), do: url
|
||||||
|
|
||||||
def url(url) do
|
def url(url) do
|
||||||
config = Application.get_env(:pleroma, :media_proxy, [])
|
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
|
url
|
||||||
else
|
else
|
||||||
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
|
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
|
||||||
base64 = Base.url_encode64(url, @base64_opts)
|
base64 = Base.url_encode64(url, @base64_opts)
|
||||||
sig = :crypto.hmac(:sha, secret, base64)
|
sig = :crypto.hmac(:sha, secret, base64)
|
||||||
sig64 = sig |> Base.url_encode64(@base64_opts)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -22,11 +23,11 @@ def decode_url(sig, url) do
|
||||||
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
|
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
|
||||||
sig = Base.url_decode64!(sig, @base64_opts)
|
sig = Base.url_decode64!(sig, @base64_opts)
|
||||||
local_sig = :crypto.hmac(:sha, secret, url)
|
local_sig = :crypto.hmac(:sha, secret, url)
|
||||||
|
|
||||||
if local_sig == sig do
|
if local_sig == sig do
|
||||||
{:ok, Base.url_decode64!(url, @base64_opts)}
|
{:ok, Base.url_decode64!(url, @base64_opts)}
|
||||||
else
|
else
|
||||||
{:error, :invalid_signature}
|
{:error, :invalid_signature}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,25 +3,26 @@ defmodule Pleroma.Web.OAuth.App do
|
||||||
import Ecto.{Changeset}
|
import Ecto.{Changeset}
|
||||||
|
|
||||||
schema "apps" do
|
schema "apps" do
|
||||||
field :client_name, :string
|
field(:client_name, :string)
|
||||||
field :redirect_uris, :string
|
field(:redirect_uris, :string)
|
||||||
field :scopes, :string
|
field(:scopes, :string)
|
||||||
field :website, :string
|
field(:website, :string)
|
||||||
field :client_id, :string
|
field(:client_id, :string)
|
||||||
field :client_secret, :string
|
field(:client_secret, :string)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_changeset(struct, params \\ %{}) do
|
def register_changeset(struct, params \\ %{}) do
|
||||||
changeset = struct
|
changeset =
|
||||||
|> cast(params, [:client_name, :redirect_uris, :scopes, :website])
|
struct
|
||||||
|> validate_required([:client_name, :redirect_uris, :scopes])
|
|> cast(params, [:client_name, :redirect_uris, :scopes, :website])
|
||||||
|
|> validate_required([:client_name, :redirect_uris, :scopes])
|
||||||
|
|
||||||
if changeset.valid? do
|
if changeset.valid? do
|
||||||
changeset
|
changeset
|
||||||
|> put_change(:client_id, :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)
|
|> put_change(:client_secret, :crypto.strong_rand_bytes(32) |> Base.url_encode64())
|
||||||
else
|
else
|
||||||
changeset
|
changeset
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,24 +7,24 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
||||||
import Ecto.{Changeset}
|
import Ecto.{Changeset}
|
||||||
|
|
||||||
schema "oauth_authorizations" do
|
schema "oauth_authorizations" do
|
||||||
field :token, :string
|
field(:token, :string)
|
||||||
field :valid_until, :naive_datetime
|
field(:valid_until, :naive_datetime)
|
||||||
field :used, :boolean, default: false
|
field(:used, :boolean, default: false)
|
||||||
belongs_to :user, Pleroma.User
|
belongs_to(:user, Pleroma.User)
|
||||||
belongs_to :app, Pleroma.App
|
belongs_to(:app, Pleroma.App)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_authorization(%App{} = app, %User{} = user) do
|
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{
|
authorization = %Authorization{
|
||||||
token: token,
|
token: token,
|
||||||
used: false,
|
used: false,
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
app_id: app.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)
|
Repo.insert(authorization)
|
||||||
|
@ -37,11 +37,12 @@ def use_changeset(%Authorization{} = auth, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def use_token(%Authorization{used: false, valid_until: valid_until} = auth) do
|
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}))
|
Repo.update(use_changeset(auth, %{used: true}))
|
||||||
else
|
else
|
||||||
{:error, "token expired"}
|
{:error, "token expired"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def use_token(%Authorization{used: true}), do: {:error, "already used"}
|
def use_token(%Authorization{used: true}), do: {:error, "already used"}
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
defmodule Pleroma.Web.OAuth.FallbackController do
|
defmodule Pleroma.Web.OAuth.FallbackController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
alias Pleroma.Web.OAuth.OAuthController
|
alias Pleroma.Web.OAuth.OAuthController
|
||||||
|
|
||||||
# No user/password
|
# No user/password
|
||||||
def call(conn, _) do
|
def call(conn, _) do
|
||||||
conn
|
conn
|
||||||
|> put_flash(:error, "Invalid Username/Password")
|
|> put_flash(:error, "Invalid Username/Password")
|
||||||
|> OAuthController.authorize(conn.params)
|
|> OAuthController.authorize(conn.params)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
|
||||||
|
|
|
@ -5,38 +5,49 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||||
alias Pleroma.{Repo, User}
|
alias Pleroma.{Repo, User}
|
||||||
alias Comeonin.Pbkdf2
|
alias Comeonin.Pbkdf2
|
||||||
|
|
||||||
plug :fetch_session
|
plug(:fetch_session)
|
||||||
plug :fetch_flash
|
plug(:fetch_flash)
|
||||||
|
|
||||||
action_fallback Pleroma.Web.OAuth.FallbackController
|
action_fallback(Pleroma.Web.OAuth.FallbackController)
|
||||||
|
|
||||||
def authorize(conn, params) do
|
def authorize(conn, params) do
|
||||||
render conn, "show.html", %{
|
render(conn, "show.html", %{
|
||||||
response_type: params["response_type"],
|
response_type: params["response_type"],
|
||||||
client_id: params["client_id"],
|
client_id: params["client_id"],
|
||||||
scope: params["scope"],
|
scope: params["scope"],
|
||||||
redirect_uri: params["redirect_uri"],
|
redirect_uri: params["redirect_uri"],
|
||||||
state: params["state"]
|
state: params["state"]
|
||||||
}
|
})
|
||||||
end
|
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),
|
with %User{} = user <- User.get_cached_by_nickname(name),
|
||||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||||
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
||||||
{:ok, auth} <- Authorization.create_authorization(app, user) do
|
{:ok, auth} <- Authorization.create_authorization(app, user) do
|
||||||
if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do
|
if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do
|
||||||
render conn, "results.html", %{
|
render(conn, "results.html", %{
|
||||||
auth: auth
|
auth: auth
|
||||||
}
|
})
|
||||||
else
|
else
|
||||||
connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
|
connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
|
||||||
url = "#{redirect_uri}#{connector}code=#{auth.token}"
|
url = "#{redirect_uri}#{connector}code=#{auth.token}"
|
||||||
url = if params["state"] do
|
|
||||||
url <> "&state=#{params["state"]}"
|
url =
|
||||||
else
|
if params["state"] do
|
||||||
url
|
url <> "&state=#{params["state"]}"
|
||||||
end
|
else
|
||||||
|
url
|
||||||
|
end
|
||||||
|
|
||||||
redirect(conn, external: url)
|
redirect(conn, external: url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -45,7 +56,12 @@ def create_authorization(conn, %{"authorization" => %{"name" => name, "password"
|
||||||
# TODO
|
# TODO
|
||||||
# - proper scope handling
|
# - proper scope handling
|
||||||
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
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"]),
|
fixed_token = fix_padding(params["code"]),
|
||||||
%Authorization{} = auth <- Repo.get_by(Authorization, token: fixed_token, app_id: app.id),
|
%Authorization{} = auth <- Repo.get_by(Authorization, token: fixed_token, app_id: app.id),
|
||||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
{: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,
|
expires_in: 60 * 10,
|
||||||
scope: "read write follow"
|
scope: "read write follow"
|
||||||
}
|
}
|
||||||
|
|
||||||
json(conn, response)
|
json(conn, response)
|
||||||
else
|
else
|
||||||
_error -> json(conn, %{error: "Invalid credentials"})
|
_error -> json(conn, %{error: "Invalid credentials"})
|
||||||
|
@ -64,8 +81,16 @@ def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
# - investigate a way to verify the user wants to grant read/write/follow once scope handling is done
|
# - 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
|
def token_exchange(
|
||||||
with %App{} = app <- Repo.get_by(App, client_id: params["client_id"], client_secret: params["client_secret"]),
|
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),
|
%User{} = user <- User.get_cached_by_nickname(name),
|
||||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||||
{:ok, auth} <- Authorization.create_authorization(app, user),
|
{: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,
|
expires_in: 60 * 10,
|
||||||
scope: "read write follow"
|
scope: "read write follow"
|
||||||
}
|
}
|
||||||
|
|
||||||
json(conn, response)
|
json(conn, response)
|
||||||
else
|
else
|
||||||
_error -> json(conn, %{error: "Invalid credentials"})
|
_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
|
defp fix_padding(token) do
|
||||||
token
|
token
|
||||||
|> Base.url_decode64!(padding: false)
|
|> Base.url_decode64!(padding: false)
|
||||||
|> Base.url_encode64
|
|> Base.url_encode64()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,11 +5,11 @@ defmodule Pleroma.Web.OAuth.Token do
|
||||||
alias Pleroma.Web.OAuth.{Token, App, Authorization}
|
alias Pleroma.Web.OAuth.{Token, App, Authorization}
|
||||||
|
|
||||||
schema "oauth_tokens" do
|
schema "oauth_tokens" do
|
||||||
field :token, :string
|
field(:token, :string)
|
||||||
field :refresh_token, :string
|
field(:refresh_token, :string)
|
||||||
field :valid_until, :naive_datetime
|
field(:valid_until, :naive_datetime)
|
||||||
belongs_to :user, Pleroma.User
|
belongs_to(:user, Pleroma.User)
|
||||||
belongs_to :app, Pleroma.App
|
belongs_to(:app, Pleroma.App)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
@ -22,15 +22,15 @@ def exchange_token(app, auth) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_token(%App{} = app, %User{} = user) do
|
def create_token(%App{} = app, %User{} = user) do
|
||||||
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
|
refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64()
|
||||||
|
|
||||||
token = %Token{
|
token = %Token{
|
||||||
token: token,
|
token: token,
|
||||||
refresh_token: refresh_token,
|
refresh_token: refresh_token,
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
app_id: app.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)
|
Repo.insert(token)
|
||||||
|
|
|
@ -5,7 +5,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
defp get_href(id) do
|
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
|
external_url
|
||||||
else
|
else
|
||||||
_e -> id
|
_e -> id
|
||||||
|
@ -13,42 +13,60 @@ defp get_href(id) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_in_reply_to(%{"object" => %{"inReplyTo" => in_reply_to}}) do
|
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
|
end
|
||||||
|
|
||||||
defp get_in_reply_to(_), do: []
|
defp get_in_reply_to(_), do: []
|
||||||
|
|
||||||
defp get_mentions(to) do
|
defp get_mentions(to) do
|
||||||
Enum.map(to, fn (id) ->
|
Enum.map(to, fn id ->
|
||||||
cond do
|
cond do
|
||||||
# Special handling for the AP/Ostatus public collections
|
# Special handling for the AP/Ostatus public collections
|
||||||
"https://www.w3.org/ns/activitystreams#Public" == id ->
|
"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.
|
# 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 ->
|
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)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_links(%{local: true, data: data}) do
|
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: ['application/atom+xml'], href: h.(data["object"]["id"]), rel: 'self'], []},
|
||||||
{:link, [type: ['text/html'], href: h.(data["object"]["id"]), rel: 'alternate'], []}
|
{:link, [type: ['text/html'], href: h.(data["object"]["id"]), rel: 'alternate'], []}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_links(%{local: false,
|
defp get_links(%{
|
||||||
data: %{
|
local: false,
|
||||||
"object" => %{
|
data: %{
|
||||||
"external_url" => external_url
|
"object" => %{
|
||||||
}
|
"external_url" => external_url
|
||||||
}}) do
|
}
|
||||||
|
}
|
||||||
|
}) 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'], []}
|
{: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_links(_activity), do: []
|
||||||
|
|
||||||
defp get_emoji_links(emojis) 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)], []}
|
{:link, [name: to_charlist(emoji), rel: 'emoji', href: to_charlist(file)], []}
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_simple_form(activity, user, with_author \\ false)
|
def to_simple_form(activity, user, with_author \\ false)
|
||||||
|
|
||||||
def to_simple_form(%{data: %{"object" => %{"type" => "Note"}}} = activity, user, with_author) do
|
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"]
|
updated_at = activity.data["object"]["published"]
|
||||||
inserted_at = activity.data["object"]["published"]
|
inserted_at = activity.data["object"]["published"]
|
||||||
|
|
||||||
attachments = Enum.map(activity.data["object"]["attachment"] || [], fn(attachment) ->
|
attachments =
|
||||||
url = hd(attachment["url"])
|
Enum.map(activity.data["object"]["attachment"] || [], fn attachment ->
|
||||||
{:link, [rel: 'enclosure', href: to_charlist(url["href"]), type: to_charlist(url["mediaType"])], []}
|
url = hd(attachment["url"])
|
||||||
end)
|
|
||||||
|
{:link,
|
||||||
|
[rel: 'enclosure', href: to_charlist(url["href"]), type: to_charlist(url["mediaType"])],
|
||||||
|
[]}
|
||||||
|
end)
|
||||||
|
|
||||||
in_reply_to = get_in_reply_to(activity.data)
|
in_reply_to = get_in_reply_to(activity.data)
|
||||||
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
||||||
mentions = activity.recipients |> get_mentions
|
mentions = activity.recipients |> get_mentions
|
||||||
|
|
||||||
categories = (activity.data["object"]["tag"] || [])
|
categories =
|
||||||
|> Enum.map(fn (tag) ->
|
(activity.data["object"]["tag"] || [])
|
||||||
if is_binary(tag) do
|
|> Enum.map(fn tag ->
|
||||||
{:category, [term: to_charlist(tag)], []}
|
if is_binary(tag) do
|
||||||
else
|
{:category, [term: to_charlist(tag)], []}
|
||||||
nil
|
else
|
||||||
end
|
nil
|
||||||
end)
|
end
|
||||||
|> Enum.filter(&(&1))
|
end)
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
|
||||||
emoji_links = get_emoji_links(activity.data["object"]["emoji"] || %{})
|
emoji_links = get_emoji_links(activity.data["object"]["emoji"] || %{})
|
||||||
|
|
||||||
summary = if activity.data["object"]["summary"] do
|
summary =
|
||||||
[{:summary, [], h.(activity.data["object"]["summary"])}]
|
if activity.data["object"]["summary"] do
|
||||||
else
|
[{:summary, [], h.(activity.data["object"]["summary"])}]
|
||||||
[]
|
else
|
||||||
end
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
[
|
[
|
||||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/note']},
|
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/note']},
|
||||||
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/post']},
|
{:"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}']},
|
{: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)},
|
{:published, h.(inserted_at)},
|
||||||
{:updated, h.(updated_at)},
|
{:updated, h.(updated_at)},
|
||||||
{:"ostatus:conversation", [ref: h.(activity.data["context"])], h.(activity.data["context"])},
|
{:"ostatus:conversation", [ref: h.(activity.data["context"])], h.(activity.data["context"])},
|
||||||
{:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []},
|
{:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []}
|
||||||
] ++ summary ++ get_links(activity) ++ categories ++ attachments ++ in_reply_to ++ author ++ mentions ++ emoji_links
|
] ++
|
||||||
|
summary ++
|
||||||
|
get_links(activity) ++
|
||||||
|
categories ++ attachments ++ in_reply_to ++ author ++ mentions ++ emoji_links
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_simple_form(%{data: %{"type" => "Like"}} = activity, user, with_author) do
|
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"]
|
updated_at = activity.data["published"]
|
||||||
inserted_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']},
|
{:content, [type: 'html'], ['#{user.nickname} favorited something']},
|
||||||
{:published, h.(inserted_at)},
|
{:published, h.(inserted_at)},
|
||||||
{:updated, h.(updated_at)},
|
{:updated, h.(updated_at)},
|
||||||
{:"activity:object", [
|
{:"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-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"])},
|
{:"ostatus:conversation", [ref: h.(activity.data["context"])], h.(activity.data["context"])},
|
||||||
{:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []},
|
{:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []},
|
||||||
{:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []},
|
{: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
|
end
|
||||||
|
|
||||||
def to_simple_form(%{data: %{"type" => "Announce"}} = activity, user, with_author) do
|
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"]
|
updated_at = activity.data["published"]
|
||||||
inserted_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)
|
retweeted_xml = to_simple_form(retweeted_activity, retweeted_user, true)
|
||||||
|
|
||||||
mentions = activity.recipients |> get_mentions
|
mentions = activity.recipients |> get_mentions
|
||||||
|
|
||||||
[
|
[
|
||||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
||||||
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/share']},
|
{:"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
|
end
|
||||||
|
|
||||||
def to_simple_form(%{data: %{"type" => "Follow"}} = activity, user, with_author) do
|
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"]
|
updated_at = activity.data["published"]
|
||||||
inserted_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: []
|
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
||||||
|
|
||||||
mentions = (activity.recipients || []) |> get_mentions
|
mentions = (activity.recipients || []) |> get_mentions
|
||||||
|
|
||||||
[
|
[
|
||||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
||||||
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/follow']},
|
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/follow']},
|
||||||
{:id, h.(activity.data["id"])},
|
{:id, h.(activity.data["id"])},
|
||||||
{:title, ['#{user.nickname} started following #{activity.data["object"]}']},
|
{: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)},
|
{:published, h.(inserted_at)},
|
||||||
{:updated, h.(updated_at)},
|
{:updated, h.(updated_at)},
|
||||||
{:"activity:object", [
|
{:"activity:object",
|
||||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/person']},
|
[
|
||||||
{:id, h.(activity.data["object"])},
|
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/person']},
|
||||||
{:uri, h.(activity.data["object"])},
|
{:id, h.(activity.data["object"])},
|
||||||
]},
|
{:uri, h.(activity.data["object"])}
|
||||||
{:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []},
|
]},
|
||||||
|
{:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []}
|
||||||
] ++ mentions ++ author
|
] ++ mentions ++ author
|
||||||
end
|
end
|
||||||
|
|
||||||
# Only undos of follow for now. Will need to get redone once there are more
|
# 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
|
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"]
|
updated_at = activity.data["published"]
|
||||||
inserted_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"])
|
follow_activity = Activity.get_by_ap_id(activity.data["object"])
|
||||||
|
|
||||||
mentions = (activity.recipients || []) |> get_mentions
|
mentions = (activity.recipients || []) |> get_mentions
|
||||||
|
|
||||||
[
|
[
|
||||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
||||||
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/unfollow']},
|
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/unfollow']},
|
||||||
{:id, h.(activity.data["id"])},
|
{:id, h.(activity.data["id"])},
|
||||||
{:title, ['#{user.nickname} stopped following #{follow_activity.data["object"]}']},
|
{: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)},
|
{:published, h.(inserted_at)},
|
||||||
{:updated, h.(updated_at)},
|
{:updated, h.(updated_at)},
|
||||||
{:"activity:object", [
|
{:"activity:object",
|
||||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/person']},
|
[
|
||||||
{:id, h.(follow_activity.data["object"])},
|
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/person']},
|
||||||
{:uri, h.(follow_activity.data["object"])},
|
{:id, h.(follow_activity.data["object"])},
|
||||||
]},
|
{:uri, h.(follow_activity.data["object"])}
|
||||||
{:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []},
|
]},
|
||||||
|
{:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []}
|
||||||
] ++ mentions ++ author
|
] ++ mentions ++ author
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_simple_form(%{data: %{"type" => "Delete"}} = activity, user, with_author) do
|
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"]
|
updated_at = activity.data["published"]
|
||||||
inserted_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']},
|
{:content, [type: 'html'], ['An object was deleted']},
|
||||||
{:published, h.(inserted_at)},
|
{:published, h.(inserted_at)},
|
||||||
{:updated, h.(updated_at)}
|
{:updated, h.(updated_at)}
|
||||||
] ++ author
|
] ++ author
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_simple_form(_, _, _), do: nil
|
def to_simple_form(_, _, _), do: nil
|
||||||
|
|
||||||
def wrap_with_entry(simple_form) do
|
def wrap_with_entry(simple_form) do
|
||||||
[{
|
[
|
||||||
:entry, [
|
{
|
||||||
xmlns: 'http://www.w3.org/2005/Atom',
|
:entry,
|
||||||
"xmlns:thr": 'http://purl.org/syndication/thread/1.0',
|
[
|
||||||
"xmlns:activity": 'http://activitystrea.ms/spec/1.0/',
|
xmlns: 'http://www.w3.org/2005/Atom',
|
||||||
"xmlns:poco": 'http://portablecontacts.net/spec/1.0',
|
"xmlns:thr": 'http://purl.org/syndication/thread/1.0',
|
||||||
"xmlns:ostatus": 'http://ostatus.org/schema/1.0'
|
"xmlns:activity": 'http://activitystrea.ms/spec/1.0/',
|
||||||
], simple_form
|
"xmlns:poco": 'http://portablecontacts.net/spec/1.0',
|
||||||
}]
|
"xmlns:ostatus": 'http://ostatus.org/schema/1.0'
|
||||||
|
],
|
||||||
|
simple_form
|
||||||
|
}
|
||||||
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,44 +5,57 @@ defmodule Pleroma.Web.OStatus.FeedRepresenter do
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
|
|
||||||
def to_simple_form(user, activities, _users) do
|
def to_simple_form(user, activities, _users) do
|
||||||
most_recent_update = (List.first(activities) || user).updated_at
|
most_recent_update =
|
||||||
|> NaiveDateTime.to_iso8601
|
(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)
|
last_activity = List.last(activities)
|
||||||
|
|
||||||
entries = activities
|
entries =
|
||||||
|> Enum.map(fn(activity) ->
|
activities
|
||||||
{:entry, ActivityRepresenter.to_simple_form(activity, user)}
|
|> Enum.map(fn activity ->
|
||||||
end)
|
{:entry, ActivityRepresenter.to_simple_form(activity, user)}
|
||||||
|> Enum.filter(fn ({_, form}) -> form end)
|
end)
|
||||||
|
|> Enum.filter(fn {_, form} -> form end)
|
||||||
|
|
||||||
[{
|
[
|
||||||
:feed, [
|
{
|
||||||
xmlns: 'http://www.w3.org/2005/Atom',
|
:feed,
|
||||||
"xmlns:thr": 'http://purl.org/syndication/thread/1.0',
|
[
|
||||||
"xmlns:activity": 'http://activitystrea.ms/spec/1.0/',
|
xmlns: 'http://www.w3.org/2005/Atom',
|
||||||
"xmlns:poco": 'http://portablecontacts.net/spec/1.0',
|
"xmlns:thr": 'http://purl.org/syndication/thread/1.0',
|
||||||
"xmlns:ostatus": 'http://ostatus.org/schema/1.0'
|
"xmlns:activity": 'http://activitystrea.ms/spec/1.0/',
|
||||||
], [
|
"xmlns:poco": 'http://portablecontacts.net/spec/1.0',
|
||||||
{:id, h.(OStatus.feed_path(user))},
|
"xmlns:ostatus": 'http://ostatus.org/schema/1.0'
|
||||||
{:title, ['#{user.nickname}\'s timeline']},
|
],
|
||||||
{:updated, h.(most_recent_update)},
|
[
|
||||||
{:logo, [to_charlist(User.avatar_url(user) |> MediaProxy.url())]},
|
{:id, h.(OStatus.feed_path(user))},
|
||||||
{:link, [rel: 'hub', href: h.(OStatus.pubsub_path(user))], []},
|
{:title, ['#{user.nickname}\'s timeline']},
|
||||||
{:link, [rel: 'salmon', href: h.(OStatus.salmon_path(user))], []},
|
{:updated, h.(most_recent_update)},
|
||||||
{:link, [rel: 'self', href: h.(OStatus.feed_path(user)), type: 'application/atom+xml'], []},
|
{:logo, [to_charlist(User.avatar_url(user) |> MediaProxy.url())]},
|
||||||
{:author, UserRepresenter.to_simple_form(user)},
|
{:link, [rel: 'hub', href: h.(OStatus.pubsub_path(user))], []},
|
||||||
] ++
|
{:link, [rel: 'salmon', href: h.(OStatus.salmon_path(user))], []},
|
||||||
if last_activity do
|
{:link, [rel: 'self', href: h.(OStatus.feed_path(user)), type: 'application/atom+xml'],
|
||||||
[{:link, [rel: 'next',
|
[]},
|
||||||
href: to_charlist(OStatus.feed_path(user)) ++ '?max_id=' ++ to_charlist(last_activity.id),
|
{:author, UserRepresenter.to_simple_form(user)}
|
||||||
type: 'application/atom+xml'], []}]
|
] ++
|
||||||
else
|
if last_activity do
|
||||||
[]
|
[
|
||||||
end
|
{:link,
|
||||||
++ entries
|
[
|
||||||
}]
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,8 @@ defmodule Pleroma.Web.OStatus.FollowHandler do
|
||||||
def handle(entry, doc) do
|
def handle(entry, doc) do
|
||||||
with {:ok, actor} <- OStatus.find_make_or_update_user(doc),
|
with {:ok, actor} <- OStatus.find_make_or_update_user(doc),
|
||||||
id when not is_nil(id) <- XML.string_from_xpath("/entry/id", entry),
|
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, followed} <- OStatus.find_or_make_user(followed_uri),
|
||||||
{:ok, activity} <- ActivityPub.follow(actor, followed, id, false) do
|
{:ok, activity} <- ActivityPub.follow(actor, followed, id, false) do
|
||||||
User.follow(actor, followed)
|
User.follow(actor, followed)
|
||||||
|
|
|
@ -13,49 +13,56 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
|
||||||
3. A newly generated context id.
|
3. A newly generated context id.
|
||||||
"""
|
"""
|
||||||
def get_context(entry, inReplyTo) do
|
def get_context(entry, inReplyTo) do
|
||||||
context = (
|
context =
|
||||||
XML.string_from_xpath("//ostatus:conversation[1]", entry)
|
(XML.string_from_xpath("//ostatus:conversation[1]", entry) ||
|
||||||
|| XML.string_from_xpath("//ostatus:conversation[1]/@ref", entry)
|
XML.string_from_xpath("//ostatus:conversation[1]/@ref", entry) || "")
|
||||||
|| "") |> String.trim
|
|> String.trim()
|
||||||
|
|
||||||
with %{data: %{"context" => context}} <- Object.get_cached_by_ap_id(inReplyTo) do
|
with %{data: %{"context" => context}} <- Object.get_cached_by_ap_id(inReplyTo) do
|
||||||
context
|
context
|
||||||
else _e ->
|
else
|
||||||
if String.length(context) > 0 do
|
_e ->
|
||||||
context
|
if String.length(context) > 0 do
|
||||||
else
|
context
|
||||||
Utils.generate_context_id
|
else
|
||||||
end
|
Utils.generate_context_id()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_people_mentions(entry) do
|
def get_people_mentions(entry) do
|
||||||
:xmerl_xpath.string('//link[@rel="mentioned" and @ostatus:object-type="http://activitystrea.ms/schema/1.0/person"]', entry)
|
:xmerl_xpath.string(
|
||||||
|> Enum.map(fn(person) -> XML.string_from_xpath("@href", person) end)
|
'//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
|
end
|
||||||
|
|
||||||
def get_collection_mentions(entry) do
|
def get_collection_mentions(entry) do
|
||||||
transmogrify = fn
|
transmogrify = fn
|
||||||
("http://activityschema.org/collection/public") ->
|
"http://activityschema.org/collection/public" ->
|
||||||
"https://www.w3.org/ns/activitystreams#Public"
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
(group) ->
|
|
||||||
|
group ->
|
||||||
group
|
group
|
||||||
end
|
end
|
||||||
|
|
||||||
:xmerl_xpath.string('//link[@rel="mentioned" and @ostatus:object-type="http://activitystrea.ms/schema/1.0/collection"]', entry)
|
:xmerl_xpath.string(
|
||||||
|> Enum.map(fn(collection) -> XML.string_from_xpath("@href", collection) |> transmogrify.() end)
|
'//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
|
end
|
||||||
|
|
||||||
def get_mentions(entry) do
|
def get_mentions(entry) do
|
||||||
(get_people_mentions(entry)
|
(get_people_mentions(entry) ++ get_collection_mentions(entry))
|
||||||
++ get_collection_mentions(entry))
|
|> Enum.filter(& &1)
|
||||||
|> Enum.filter(&(&1))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_emoji(entry) do
|
def get_emoji(entry) do
|
||||||
try do
|
try do
|
||||||
:xmerl_xpath.string('//link[@rel="emoji"]', entry)
|
: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))
|
Map.put(acc, XML.string_from_xpath("@name", emoji), XML.string_from_xpath("@href", emoji))
|
||||||
end)
|
end)
|
||||||
rescue
|
rescue
|
||||||
|
@ -79,7 +86,8 @@ def fetch_replied_to_activity(entry, inReplyTo) do
|
||||||
activity
|
activity
|
||||||
else
|
else
|
||||||
_e ->
|
_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
|
{:ok, [activity | _]} <- OStatus.fetch_activity_from_url(inReplyToHref) do
|
||||||
activity
|
activity
|
||||||
else
|
else
|
||||||
|
@ -107,16 +115,40 @@ def handle_note(entry, doc \\ nil) do
|
||||||
date <- XML.string_from_xpath("//published", entry),
|
date <- XML.string_from_xpath("//published", entry),
|
||||||
unlisted <- XML.string_from_xpath("//mastodon:scope", entry) == "unlisted",
|
unlisted <- XML.string_from_xpath("//mastodon:scope", entry) == "unlisted",
|
||||||
cc <- if(unlisted, do: ["https://www.w3.org/ns/activitystreams#Public"], else: []),
|
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("id", id) |> Map.put("tag", tags),
|
||||||
note <- note |> Map.put("published", date),
|
note <- note |> Map.put("published", date),
|
||||||
note <- note |> Map.put("emoji", get_emoji(entry)),
|
note <- note |> Map.put("emoji", get_emoji(entry)),
|
||||||
note <- add_external_url(note, entry),
|
note <- add_external_url(note, entry),
|
||||||
note <- note |> Map.put("cc", cc),
|
note <- note |> Map.put("cc", cc),
|
||||||
# TODO: Handle this case in make_note_data
|
# TODO: Handle this case in make_note_data
|
||||||
note <- (if inReplyTo && !inReplyToActivity, do: note |> Map.put("inReplyTo", inReplyTo), else: note)
|
note <-
|
||||||
do
|
if(
|
||||||
res = ActivityPub.create(%{to: to, actor: actor, context: context, object: note, published: date, local: false, additional: %{"cc" => cc}})
|
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)
|
User.increase_note_count(actor)
|
||||||
res
|
res
|
||||||
else
|
else
|
||||||
|
|
|
@ -16,7 +16,7 @@ def feed_path(user) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def pubsub_path(user) do
|
def pubsub_path(user) do
|
||||||
"#{Web.base_url}/push/hub/#{user.nickname}"
|
"#{Web.base_url()}/push/hub/#{user.nickname}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def salmon_path(user) do
|
def salmon_path(user) do
|
||||||
|
@ -24,48 +24,59 @@ def salmon_path(user) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def remote_follow_path do
|
def remote_follow_path do
|
||||||
"#{Web.base_url}/ostatus_subscribe?acct={uri}"
|
"#{Web.base_url()}/ostatus_subscribe?acct={uri}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(xml_string) do
|
def handle_incoming(xml_string) do
|
||||||
with doc when doc != :error <- parse_document(xml_string) do
|
with doc when doc != :error <- parse_document(xml_string) do
|
||||||
entries = :xmerl_xpath.string('//entry', doc)
|
entries = :xmerl_xpath.string('//entry', doc)
|
||||||
|
|
||||||
activities = Enum.map(entries, fn (entry) ->
|
activities =
|
||||||
{:xmlObj, :string, object_type} = :xmerl_xpath.string('string(/entry/activity:object-type[1])', entry)
|
Enum.map(entries, fn entry ->
|
||||||
{:xmlObj, :string, verb} = :xmerl_xpath.string('string(/entry/activity:verb[1])', entry)
|
{:xmlObj, :string, object_type} =
|
||||||
Logger.debug("Handling #{verb}")
|
:xmerl_xpath.string('string(/entry/activity:object-type[1])', entry)
|
||||||
|
|
||||||
try do
|
{:xmlObj, :string, verb} = :xmerl_xpath.string('string(/entry/activity:verb[1])', entry)
|
||||||
case verb do
|
Logger.debug("Handling #{verb}")
|
||||||
'http://activitystrea.ms/schema/1.0/delete' ->
|
|
||||||
with {:ok, activity} <- DeleteHandler.handle_delete(entry, doc), do: activity
|
try do
|
||||||
'http://activitystrea.ms/schema/1.0/follow' ->
|
case verb do
|
||||||
with {:ok, activity} <- FollowHandler.handle(entry, doc), do: activity
|
'http://activitystrea.ms/schema/1.0/delete' ->
|
||||||
'http://activitystrea.ms/schema/1.0/share' ->
|
with {:ok, activity} <- DeleteHandler.handle_delete(entry, doc), do: activity
|
||||||
with {:ok, activity, retweeted_activity} <- handle_share(entry, doc), do: [activity, retweeted_activity]
|
|
||||||
'http://activitystrea.ms/schema/1.0/favorite' ->
|
'http://activitystrea.ms/schema/1.0/follow' ->
|
||||||
with {:ok, activity, favorited_activity} <- handle_favorite(entry, doc), do: [activity, favorited_activity]
|
with {:ok, activity} <- FollowHandler.handle(entry, doc), do: activity
|
||||||
_ ->
|
|
||||||
case object_type do
|
'http://activitystrea.ms/schema/1.0/share' ->
|
||||||
'http://activitystrea.ms/schema/1.0/note' ->
|
with {:ok, activity, retweeted_activity} <- handle_share(entry, doc),
|
||||||
with {:ok, activity} <- NoteHandler.handle_note(entry, doc), do: activity
|
do: [activity, retweeted_activity]
|
||||||
'http://activitystrea.ms/schema/1.0/comment' ->
|
|
||||||
with {:ok, activity} <- NoteHandler.handle_note(entry, doc), do: activity
|
'http://activitystrea.ms/schema/1.0/favorite' ->
|
||||||
_ ->
|
with {:ok, activity, favorited_activity} <- handle_favorite(entry, doc),
|
||||||
Logger.error("Couldn't parse incoming document")
|
do: [activity, favorited_activity]
|
||||||
nil
|
|
||||||
end
|
_ ->
|
||||||
|
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
|
end
|
||||||
rescue
|
end)
|
||||||
e ->
|
|> Enum.filter(& &1)
|
||||||
Logger.error("Error occured while handling activity")
|
|
||||||
Logger.error(xml_string)
|
|
||||||
Logger.error(inspect(e))
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|> Enum.filter(&(&1))
|
|
||||||
|
|
||||||
{:ok, activities}
|
{:ok, activities}
|
||||||
else
|
else
|
||||||
|
@ -113,15 +124,20 @@ def get_or_build_object(entry) do
|
||||||
|
|
||||||
def get_or_try_fetching(entry) do
|
def get_or_try_fetching(entry) do
|
||||||
Logger.debug("Trying to get entry from db")
|
Logger.debug("Trying to get entry from db")
|
||||||
|
|
||||||
with id when not is_nil(id) <- string_from_xpath("//activity:object[1]/id", entry),
|
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
|
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else _ ->
|
else
|
||||||
|
_ ->
|
||||||
Logger.debug("Couldn't get, will try to fetch")
|
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]} <- fetch_activity_from_url(href) do
|
||||||
{:ok, favorited_activity}
|
{: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
|
end
|
||||||
end
|
end
|
||||||
|
@ -137,20 +153,22 @@ def handle_favorite(entry, doc) do
|
||||||
|
|
||||||
def get_attachments(entry) do
|
def get_attachments(entry) do
|
||||||
:xmerl_xpath.string('/entry/link[@rel="enclosure"]', entry)
|
: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),
|
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 when not is_nil(type) <- string_from_xpath("/link/@type", enclosure) do
|
||||||
%{
|
%{
|
||||||
"type" => "Attachment",
|
"type" => "Attachment",
|
||||||
"url" => [%{
|
"url" => [
|
||||||
"type" => "Link",
|
%{
|
||||||
"mediaType" => type,
|
"type" => "Link",
|
||||||
"href" => href
|
"mediaType" => type,
|
||||||
}]
|
"href" => href
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|> Enum.filter(&(&1))
|
|> Enum.filter(& &1)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -166,14 +184,15 @@ def get_content(entry) do
|
||||||
def get_cw(entry) do
|
def get_cw(entry) do
|
||||||
with cw when not is_nil(cw) <- string_from_xpath("/*/summary", entry) do
|
with cw when not is_nil(cw) <- string_from_xpath("/*/summary", entry) do
|
||||||
cw
|
cw
|
||||||
else _e -> nil
|
else
|
||||||
|
_e -> nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_tags(entry) do
|
def get_tags(entry) do
|
||||||
:xmerl_xpath.string('//category', entry)
|
:xmerl_xpath.string('//category', entry)
|
||||||
|> Enum.map(fn (category) -> string_from_xpath("/category/@term", category) end)
|
|> Enum.map(fn category -> string_from_xpath("/category/@term", category) end)
|
||||||
|> Enum.filter(&(&1))
|
|> Enum.filter(& &1)
|
||||||
|> Enum.map(&String.downcase/1)
|
|> Enum.map(&String.downcase/1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -184,6 +203,7 @@ def maybe_update(doc, user) do
|
||||||
maybe_update_ostatus(doc, user)
|
maybe_update_ostatus(doc, user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_update_ostatus(doc, user) do
|
def maybe_update_ostatus(doc, user) do
|
||||||
old_data = %{
|
old_data = %{
|
||||||
avatar: user.avatar,
|
avatar: user.avatar,
|
||||||
|
@ -196,26 +216,33 @@ def maybe_update_ostatus(doc, user) do
|
||||||
avatar <- make_avatar_object(doc),
|
avatar <- make_avatar_object(doc),
|
||||||
bio <- string_from_xpath("//author[1]/summary", doc),
|
bio <- string_from_xpath("//author[1]/summary", doc),
|
||||||
name <- string_from_xpath("//author[1]/poco:displayName", doc),
|
name <- string_from_xpath("//author[1]/poco:displayName", doc),
|
||||||
info <- Map.put(user.info, "banner", make_avatar_object(doc, "header") || user.info["banner"]),
|
info <-
|
||||||
new_data <- %{avatar: avatar || old_data.avatar, name: name || old_data.name, bio: bio || old_data.bio, info: info || old_data.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
|
false <- new_data == old_data do
|
||||||
change = Ecto.Changeset.change(user, new_data)
|
change = Ecto.Changeset.change(user, new_data)
|
||||||
Repo.update(change)
|
Repo.update(change)
|
||||||
else _ ->
|
else
|
||||||
{:ok, user}
|
_ ->
|
||||||
|
{:ok, user}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_make_or_update_user(doc) do
|
def find_make_or_update_user(doc) do
|
||||||
uri = string_from_xpath("//author/uri[1]", doc)
|
uri = string_from_xpath("//author/uri[1]", doc)
|
||||||
|
|
||||||
with {:ok, user} <- find_or_make_user(uri) do
|
with {:ok, user} <- find_or_make_user(uri) do
|
||||||
maybe_update(doc, user)
|
maybe_update(doc, user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_or_make_user(uri) do
|
def find_or_make_user(uri) do
|
||||||
query = from user in User,
|
query = from(user in User, where: user.ap_id == ^uri)
|
||||||
where: user.ap_id == ^uri
|
|
||||||
|
|
||||||
user = Repo.one(query)
|
user = Repo.one(query)
|
||||||
|
|
||||||
|
@ -236,10 +263,12 @@ def make_user(uri, update \\ false) do
|
||||||
avatar: info["avatar"],
|
avatar: info["avatar"],
|
||||||
bio: info["bio"]
|
bio: info["bio"]
|
||||||
}
|
}
|
||||||
|
|
||||||
with false <- update,
|
with false <- update,
|
||||||
%User{} = user <- User.get_by_ap_id(data.ap_id) do
|
%User{} = user <- User.get_by_ap_id(data.ap_id) do
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else _e -> User.insert_or_update_user(data)
|
else
|
||||||
|
_e -> User.insert_or_update_user(data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -252,12 +281,13 @@ def make_avatar_object(author_doc, rel \\ "avatar") do
|
||||||
if href do
|
if href do
|
||||||
%{
|
%{
|
||||||
"type" => "Image",
|
"type" => "Image",
|
||||||
"url" =>
|
"url" => [
|
||||||
[%{
|
%{
|
||||||
"type" => "Link",
|
"type" => "Link",
|
||||||
"mediaType" => type,
|
"mediaType" => type,
|
||||||
"href" => href
|
"href" => href
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
nil
|
nil
|
||||||
|
@ -268,9 +298,10 @@ def gather_user_info(username) do
|
||||||
with {:ok, webfinger_data} <- WebFinger.finger(username),
|
with {:ok, webfinger_data} <- WebFinger.finger(username),
|
||||||
{:ok, feed_data} <- Websub.gather_feed_data(webfinger_data["topic"]) do
|
{:ok, feed_data} <- Websub.gather_feed_data(webfinger_data["topic"]) do
|
||||||
{:ok, Map.merge(webfinger_data, feed_data) |> Map.put("fqn", username)}
|
{:ok, Map.merge(webfinger_data, feed_data) |> Map.put("fqn", username)}
|
||||||
else e ->
|
else
|
||||||
Logger.debug(fn -> "Couldn't gather info for #{username}" end)
|
e ->
|
||||||
{:error, e}
|
Logger.debug(fn -> "Couldn't gather info for #{username}" end)
|
||||||
|
{:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -284,12 +315,15 @@ def get_atom_url(body) do
|
||||||
Regex.match?(@mastodon_regex, body) ->
|
Regex.match?(@mastodon_regex, body) ->
|
||||||
[[_, match]] = Regex.scan(@mastodon_regex, body)
|
[[_, match]] = Regex.scan(@mastodon_regex, body)
|
||||||
{:ok, match}
|
{:ok, match}
|
||||||
|
|
||||||
Regex.match?(@gs_regex, body) ->
|
Regex.match?(@gs_regex, body) ->
|
||||||
[[_, match]] = Regex.scan(@gs_regex, body)
|
[[_, match]] = Regex.scan(@gs_regex, body)
|
||||||
{:ok, match}
|
{:ok, match}
|
||||||
|
|
||||||
Regex.match?(@gs_classic_regex, body) ->
|
Regex.match?(@gs_classic_regex, body) ->
|
||||||
[[_, match]] = Regex.scan(@gs_classic_regex, body)
|
[[_, match]] = Regex.scan(@gs_classic_regex, body)
|
||||||
{:ok, match}
|
{:ok, match}
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
Logger.debug(fn -> "Couldn't find Atom link in #{inspect(body)}" end)
|
Logger.debug(fn -> "Couldn't find Atom link in #{inspect(body)}" end)
|
||||||
{:error, "Couldn't find the Atom link"}
|
{: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
|
def fetch_activity_from_atom_url(url) do
|
||||||
with true <- String.starts_with?(url, "http"),
|
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...")
|
Logger.debug("Got document from #{url}, handling...")
|
||||||
handle_incoming(body)
|
handle_incoming(body)
|
||||||
else
|
else
|
||||||
|
@ -310,10 +351,12 @@ def fetch_activity_from_atom_url(url) do
|
||||||
|
|
||||||
def fetch_activity_from_html_url(url) do
|
def fetch_activity_from_html_url(url) do
|
||||||
Logger.debug("Trying to fetch #{url}")
|
Logger.debug("Trying to fetch #{url}")
|
||||||
|
|
||||||
with true <- String.starts_with?(url, "http"),
|
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
|
{:ok, atom_url} <- get_atom_url(body) do
|
||||||
fetch_activity_from_atom_url(atom_url)
|
fetch_activity_from_atom_url(atom_url)
|
||||||
else
|
else
|
||||||
e ->
|
e ->
|
||||||
Logger.debug("Couldn't get #{url}: #{inspect(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
|
with {:ok, activities} when length(activities) > 0 <- fetch_activity_from_atom_url(url) do
|
||||||
{:ok, activities}
|
{:ok, activities}
|
||||||
else
|
else
|
||||||
_e -> with {:ok, activities} <- fetch_activity_from_html_url(url) do
|
_e ->
|
||||||
{:ok, activities}
|
with {:ok, activities} <- fetch_activity_from_html_url(url) do
|
||||||
end
|
{:ok, activities}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
e ->
|
e ->
|
||||||
|
|
|
@ -16,23 +16,26 @@ def feed_redirect(conn, %{"nickname" => nickname} = params) do
|
||||||
case get_format(conn) do
|
case get_format(conn) do
|
||||||
"html" -> Fallback.RedirectController.redirector(conn, nil)
|
"html" -> Fallback.RedirectController.redirector(conn, nil)
|
||||||
"activity+json" -> ActivityPubController.user(conn, params)
|
"activity+json" -> ActivityPubController.user(conn, params)
|
||||||
_ -> redirect conn, external: OStatus.feed_path(user)
|
_ -> redirect(conn, external: OStatus.feed_path(user))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def feed(conn, %{"nickname" => nickname} = params) do
|
def feed(conn, %{"nickname" => nickname} = params) do
|
||||||
user = User.get_cached_by_nickname(nickname)
|
user = User.get_cached_by_nickname(nickname)
|
||||||
|
|
||||||
query_params = Map.take(params, ["max_id"])
|
query_params =
|
||||||
|> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
|
Map.take(params, ["max_id"])
|
||||||
|
|> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
|
||||||
|
|
||||||
activities = ActivityPub.fetch_public_activities(query_params)
|
activities =
|
||||||
|> Enum.reverse
|
ActivityPub.fetch_public_activities(query_params)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
response = user
|
response =
|
||||||
|> FeedRepresenter.to_simple_form(activities, [user])
|
user
|
||||||
|> :xmerl.export_simple(:xmerl_xml)
|
|> FeedRepresenter.to_simple_form(activities, [user])
|
||||||
|> to_string
|
|> :xmerl.export_simple(:xmerl_xml)
|
||||||
|
|> to_string
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/atom+xml")
|
|> put_resp_content_type("application/atom+xml")
|
||||||
|
@ -73,7 +76,7 @@ def object(conn, %{"uuid" => uuid} = params) do
|
||||||
else
|
else
|
||||||
with id <- o_status_url(conn, :object, uuid),
|
with id <- o_status_url(conn, :object, uuid),
|
||||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id),
|
%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
|
case get_format(conn) do
|
||||||
"html" -> redirect(conn, to: "/notice/#{activity.id}")
|
"html" -> redirect(conn, to: "/notice/#{activity.id}")
|
||||||
_ -> represent_activity(conn, activity, user)
|
_ -> represent_activity(conn, activity, user)
|
||||||
|
@ -96,24 +99,27 @@ def activity(conn, %{"uuid" => uuid}) do
|
||||||
|
|
||||||
# TODO: Data leak
|
# TODO: Data leak
|
||||||
def notice(conn, %{"id" => id}) do
|
def notice(conn, %{"id" => id}) do
|
||||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
with %Activity{} = activity <- Repo.get(Activity, 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
|
case get_format(conn) do
|
||||||
"html" ->
|
"html" ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("text/html")
|
|> put_resp_content_type("text/html")
|
||||||
|> send_file(200, "priv/static/index.html")
|
|> send_file(200, "priv/static/index.html")
|
||||||
_ -> represent_activity(conn, activity, user)
|
|
||||||
|
_ ->
|
||||||
|
represent_activity(conn, activity, user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp represent_activity(conn, activity, user) do
|
defp represent_activity(conn, activity, user) do
|
||||||
response = activity
|
response =
|
||||||
|> ActivityRepresenter.to_simple_form(user, true)
|
activity
|
||||||
|> ActivityRepresenter.wrap_with_entry
|
|> ActivityRepresenter.to_simple_form(user, true)
|
||||||
|> :xmerl.export_simple(:xmerl_xml)
|
|> ActivityRepresenter.wrap_with_entry()
|
||||||
|> to_string
|
|> :xmerl.export_simple(:xmerl_xml)
|
||||||
|
|> to_string
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/atom+xml")
|
|> put_resp_content_type("application/atom+xml")
|
||||||
|
|
|
@ -1,22 +1,26 @@
|
||||||
defmodule Pleroma.Web.OStatus.UserRepresenter do
|
defmodule Pleroma.Web.OStatus.UserRepresenter do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
def to_simple_form(user) do
|
def to_simple_form(user) do
|
||||||
ap_id = to_charlist(user.ap_id)
|
ap_id = to_charlist(user.ap_id)
|
||||||
nickname = to_charlist(user.nickname)
|
nickname = to_charlist(user.nickname)
|
||||||
name = to_charlist(user.name)
|
name = to_charlist(user.name)
|
||||||
bio = to_charlist(user.bio)
|
bio = to_charlist(user.bio)
|
||||||
avatar_url = to_charlist(User.avatar_url(user))
|
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
|
banner =
|
||||||
[{:ap_enabled, ['true']}]
|
if banner_url = User.banner_url(user) do
|
||||||
else
|
[{:link, [rel: 'header', href: banner_url], []}]
|
||||||
[]
|
else
|
||||||
end
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
ap_enabled =
|
||||||
|
if user.local do
|
||||||
|
[{:ap_enabled, ['true']}]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
[
|
[
|
||||||
{:id, [ap_id]},
|
{:id, [ap_id]},
|
||||||
|
|
|
@ -11,294 +11,305 @@ def user_fetcher(username) do
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :api do
|
pipeline :api do
|
||||||
plug :accepts, ["json"]
|
plug(:accepts, ["json"])
|
||||||
plug :fetch_session
|
plug(:fetch_session)
|
||||||
plug Pleroma.Plugs.OAuthPlug
|
plug(Pleroma.Plugs.OAuthPlug)
|
||||||
plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true}
|
plug(Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true})
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :authenticated_api do
|
pipeline :authenticated_api do
|
||||||
plug :accepts, ["json"]
|
plug(:accepts, ["json"])
|
||||||
plug :fetch_session
|
plug(:fetch_session)
|
||||||
plug Pleroma.Plugs.OAuthPlug
|
plug(Pleroma.Plugs.OAuthPlug)
|
||||||
plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1}
|
plug(Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1})
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :mastodon_html do
|
pipeline :mastodon_html do
|
||||||
plug :accepts, ["html"]
|
plug(:accepts, ["html"])
|
||||||
plug :fetch_session
|
plug(:fetch_session)
|
||||||
plug Pleroma.Plugs.OAuthPlug
|
plug(Pleroma.Plugs.OAuthPlug)
|
||||||
plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true}
|
plug(Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true})
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :pleroma_html do
|
pipeline :pleroma_html do
|
||||||
plug :accepts, ["html"]
|
plug(:accepts, ["html"])
|
||||||
plug :fetch_session
|
plug(:fetch_session)
|
||||||
plug Pleroma.Plugs.OAuthPlug
|
plug(Pleroma.Plugs.OAuthPlug)
|
||||||
plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true}
|
plug(Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true})
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :well_known do
|
pipeline :well_known do
|
||||||
plug :accepts, ["xml", "xrd+xml", "json", "jrd+json"]
|
plug(:accepts, ["xml", "xrd+xml", "json", "jrd+json"])
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :config do
|
pipeline :config do
|
||||||
plug :accepts, ["json", "xml"]
|
plug(:accepts, ["json", "xml"])
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :oauth do
|
pipeline :oauth do
|
||||||
plug :accepts, ["html", "json"]
|
plug(:accepts, ["html", "json"])
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :pleroma_api do
|
pipeline :pleroma_api do
|
||||||
plug :accepts, ["html", "json"]
|
plug(:accepts, ["html", "json"])
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
|
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
|
||||||
pipe_through :pleroma_api
|
pipe_through(:pleroma_api)
|
||||||
get "/password_reset/:token", UtilController, :show_password_reset
|
get("/password_reset/:token", UtilController, :show_password_reset)
|
||||||
post "/password_reset", UtilController, :password_reset
|
post("/password_reset", UtilController, :password_reset)
|
||||||
get "/emoji", UtilController, :emoji
|
get("/emoji", UtilController, :emoji)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", Pleroma.Web.TwitterAPI do
|
scope "/", Pleroma.Web.TwitterAPI do
|
||||||
pipe_through :pleroma_html
|
pipe_through(:pleroma_html)
|
||||||
get "/ostatus_subscribe", UtilController, :remote_follow
|
get("/ostatus_subscribe", UtilController, :remote_follow)
|
||||||
post "/ostatus_subscribe", UtilController, :do_remote_follow
|
post("/ostatus_subscribe", UtilController, :do_remote_follow)
|
||||||
post "/main/ostatus", UtilController, :remote_subscribe
|
post("/main/ostatus", UtilController, :remote_subscribe)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
|
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
|
||||||
pipe_through :authenticated_api
|
pipe_through(:authenticated_api)
|
||||||
post "/follow_import", UtilController, :follow_import
|
post("/follow_import", UtilController, :follow_import)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/oauth", Pleroma.Web.OAuth do
|
scope "/oauth", Pleroma.Web.OAuth do
|
||||||
get "/authorize", OAuthController, :authorize
|
get("/authorize", OAuthController, :authorize)
|
||||||
post "/authorize", OAuthController, :create_authorization
|
post("/authorize", OAuthController, :create_authorization)
|
||||||
post "/token", OAuthController, :token_exchange
|
post("/token", OAuthController, :token_exchange)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||||
pipe_through :authenticated_api
|
pipe_through(:authenticated_api)
|
||||||
|
|
||||||
patch "/accounts/update_credentials", MastodonAPIController, :update_credentials
|
patch("/accounts/update_credentials", MastodonAPIController, :update_credentials)
|
||||||
get "/accounts/verify_credentials", MastodonAPIController, :verify_credentials
|
get("/accounts/verify_credentials", MastodonAPIController, :verify_credentials)
|
||||||
get "/accounts/relationships", MastodonAPIController, :relationships
|
get("/accounts/relationships", MastodonAPIController, :relationships)
|
||||||
get "/accounts/search", MastodonAPIController, :account_search
|
get("/accounts/search", MastodonAPIController, :account_search)
|
||||||
post "/accounts/:id/follow", MastodonAPIController, :follow
|
post("/accounts/:id/follow", MastodonAPIController, :follow)
|
||||||
post "/accounts/:id/unfollow", MastodonAPIController, :unfollow
|
post("/accounts/:id/unfollow", MastodonAPIController, :unfollow)
|
||||||
post "/accounts/:id/block", MastodonAPIController, :block
|
post("/accounts/:id/block", MastodonAPIController, :block)
|
||||||
post "/accounts/:id/unblock", MastodonAPIController, :unblock
|
post("/accounts/:id/unblock", MastodonAPIController, :unblock)
|
||||||
post "/accounts/:id/mute", MastodonAPIController, :relationship_noop
|
post("/accounts/:id/mute", MastodonAPIController, :relationship_noop)
|
||||||
post "/accounts/:id/unmute", 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("/domain_blocks", MastodonAPIController, :empty_array)
|
||||||
get "/follow_requests", MastodonAPIController, :empty_array
|
get("/follow_requests", MastodonAPIController, :empty_array)
|
||||||
get "/mutes", 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
|
post("/statuses", MastodonAPIController, :post_status)
|
||||||
delete "/statuses/:id", MastodonAPIController, :delete_status
|
delete("/statuses/:id", MastodonAPIController, :delete_status)
|
||||||
|
|
||||||
post "/statuses/:id/reblog", MastodonAPIController, :reblog_status
|
post("/statuses/:id/reblog", MastodonAPIController, :reblog_status)
|
||||||
post "/statuses/:id/favourite", MastodonAPIController, :fav_status
|
post("/statuses/:id/favourite", MastodonAPIController, :fav_status)
|
||||||
post "/statuses/:id/unfavourite", MastodonAPIController, :unfav_status
|
post("/statuses/:id/unfavourite", MastodonAPIController, :unfav_status)
|
||||||
|
|
||||||
post "/notifications/clear", MastodonAPIController, :clear_notifications
|
post("/notifications/clear", MastodonAPIController, :clear_notifications)
|
||||||
post "/notifications/dismiss", MastodonAPIController, :dismiss_notification
|
post("/notifications/dismiss", MastodonAPIController, :dismiss_notification)
|
||||||
get "/notifications", MastodonAPIController, :notifications
|
get("/notifications", MastodonAPIController, :notifications)
|
||||||
get "/notifications/:id", MastodonAPIController, :get_notification
|
get("/notifications/:id", MastodonAPIController, :get_notification)
|
||||||
|
|
||||||
post "/media", MastodonAPIController, :upload
|
post("/media", MastodonAPIController, :upload)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||||
pipe_through :api
|
pipe_through(:api)
|
||||||
get "/instance", MastodonAPIController, :masto_instance
|
get("/instance", MastodonAPIController, :masto_instance)
|
||||||
get "/instance/peers", MastodonAPIController, :peers
|
get("/instance/peers", MastodonAPIController, :peers)
|
||||||
post "/apps", MastodonAPIController, :create_app
|
post("/apps", MastodonAPIController, :create_app)
|
||||||
get "/custom_emojis", MastodonAPIController, :custom_emojis
|
get("/custom_emojis", MastodonAPIController, :custom_emojis)
|
||||||
|
|
||||||
get "/timelines/public", MastodonAPIController, :public_timeline
|
get("/timelines/public", MastodonAPIController, :public_timeline)
|
||||||
get "/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline
|
get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
|
||||||
|
|
||||||
get "/statuses/:id", MastodonAPIController, :get_status
|
get("/statuses/:id", MastodonAPIController, :get_status)
|
||||||
get "/statuses/:id/context", MastodonAPIController, :get_context
|
get("/statuses/:id/context", MastodonAPIController, :get_context)
|
||||||
get "/statuses/:id/card", MastodonAPIController, :empty_object
|
get("/statuses/:id/card", MastodonAPIController, :empty_object)
|
||||||
get "/statuses/:id/favourited_by", MastodonAPIController, :favourited_by
|
get("/statuses/:id/favourited_by", MastodonAPIController, :favourited_by)
|
||||||
get "/statuses/:id/reblogged_by", MastodonAPIController, :reblogged_by
|
get("/statuses/:id/reblogged_by", MastodonAPIController, :reblogged_by)
|
||||||
|
|
||||||
get "/accounts/:id/statuses", MastodonAPIController, :user_statuses
|
get("/accounts/:id/statuses", MastodonAPIController, :user_statuses)
|
||||||
get "/accounts/:id/followers", MastodonAPIController, :followers
|
get("/accounts/:id/followers", MastodonAPIController, :followers)
|
||||||
get "/accounts/:id/following", MastodonAPIController, :following
|
get("/accounts/:id/following", MastodonAPIController, :following)
|
||||||
get "/accounts/:id", MastodonAPIController, :user
|
get("/accounts/:id", MastodonAPIController, :user)
|
||||||
|
|
||||||
get "/search", MastodonAPIController, :search
|
get("/search", MastodonAPIController, :search)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api", Pleroma.Web do
|
scope "/api", Pleroma.Web do
|
||||||
pipe_through :config
|
pipe_through(:config)
|
||||||
|
|
||||||
get "/help/test", TwitterAPI.UtilController, :help_test
|
get("/help/test", TwitterAPI.UtilController, :help_test)
|
||||||
post "/help/test", TwitterAPI.UtilController, :help_test
|
post("/help/test", TwitterAPI.UtilController, :help_test)
|
||||||
get "/statusnet/config", TwitterAPI.UtilController, :config
|
get("/statusnet/config", TwitterAPI.UtilController, :config)
|
||||||
get "/statusnet/version", TwitterAPI.UtilController, :version
|
get("/statusnet/version", TwitterAPI.UtilController, :version)
|
||||||
end
|
end
|
||||||
|
|
||||||
@instance Application.get_env(:pleroma, :instance)
|
@instance Application.get_env(:pleroma, :instance)
|
||||||
@registrations_open Keyword.get(@instance, :registrations_open)
|
@registrations_open Keyword.get(@instance, :registrations_open)
|
||||||
|
|
||||||
scope "/api", Pleroma.Web do
|
scope "/api", Pleroma.Web do
|
||||||
pipe_through :api
|
pipe_through(:api)
|
||||||
|
|
||||||
get "/statuses/public_timeline", TwitterAPI.Controller, :public_timeline
|
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/followers", TwitterAPI.Controller, :followers
|
get(
|
||||||
get "/statuses/friends", TwitterAPI.Controller, :friends
|
"/statuses/public_and_external_timeline",
|
||||||
get "/statuses/show/:id", TwitterAPI.Controller, :fetch_status
|
TwitterAPI.Controller,
|
||||||
get "/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation
|
: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
|
if @registrations_open do
|
||||||
post "/account/register", TwitterAPI.Controller, :register
|
post("/account/register", TwitterAPI.Controller, :register)
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/search", TwitterAPI.Controller, :search
|
get("/search", TwitterAPI.Controller, :search)
|
||||||
get "/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline
|
get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api", Pleroma.Web do
|
scope "/api", Pleroma.Web do
|
||||||
pipe_through :authenticated_api
|
pipe_through(:authenticated_api)
|
||||||
|
|
||||||
get "/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/verify_credentials", TwitterAPI.Controller, :verify_credentials)
|
||||||
|
|
||||||
post "/account/update_profile", TwitterAPI.Controller, :update_profile
|
post("/account/update_profile", TwitterAPI.Controller, :update_profile)
|
||||||
post "/account/update_profile_banner", TwitterAPI.Controller, :update_banner
|
post("/account/update_profile_banner", TwitterAPI.Controller, :update_banner)
|
||||||
post "/qvitter/update_background_image", TwitterAPI.Controller, :update_background
|
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/home_timeline", TwitterAPI.Controller, :friends_timeline)
|
||||||
get "/statuses/friends_timeline", TwitterAPI.Controller, :friends_timeline
|
get("/statuses/friends_timeline", TwitterAPI.Controller, :friends_timeline)
|
||||||
get "/statuses/mentions", TwitterAPI.Controller, :mentions_timeline
|
get("/statuses/mentions", TwitterAPI.Controller, :mentions_timeline)
|
||||||
get "/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline
|
get("/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline)
|
||||||
|
|
||||||
post "/statuses/update", TwitterAPI.Controller, :status_update
|
post("/statuses/update", TwitterAPI.Controller, :status_update)
|
||||||
post "/statuses/retweet/:id", TwitterAPI.Controller, :retweet
|
post("/statuses/retweet/:id", TwitterAPI.Controller, :retweet)
|
||||||
post "/statuses/destroy/:id", TwitterAPI.Controller, :delete_post
|
post("/statuses/destroy/:id", TwitterAPI.Controller, :delete_post)
|
||||||
|
|
||||||
post "/friendships/create", TwitterAPI.Controller, :follow
|
post("/friendships/create", TwitterAPI.Controller, :follow)
|
||||||
post "/friendships/destroy", TwitterAPI.Controller, :unfollow
|
post("/friendships/destroy", TwitterAPI.Controller, :unfollow)
|
||||||
post "/blocks/create", TwitterAPI.Controller, :block
|
post("/blocks/create", TwitterAPI.Controller, :block)
|
||||||
post "/blocks/destroy", TwitterAPI.Controller, :unblock
|
post("/blocks/destroy", TwitterAPI.Controller, :unblock)
|
||||||
|
|
||||||
post "/statusnet/media/upload", TwitterAPI.Controller, :upload
|
post("/statusnet/media/upload", TwitterAPI.Controller, :upload)
|
||||||
post "/media/upload", TwitterAPI.Controller, :upload_json
|
post("/media/upload", TwitterAPI.Controller, :upload_json)
|
||||||
|
|
||||||
post "/favorites/create/:id", TwitterAPI.Controller, :favorite
|
post("/favorites/create/:id", TwitterAPI.Controller, :favorite)
|
||||||
post "/favorites/create", TwitterAPI.Controller, :favorite
|
post("/favorites/create", TwitterAPI.Controller, :favorite)
|
||||||
post "/favorites/destroy/:id", TwitterAPI.Controller, :unfavorite
|
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("/friends/ids", TwitterAPI.Controller, :friends_ids)
|
||||||
get "/friendships/no_retweets/ids", TwitterAPI.Controller, :empty_array
|
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
|
end
|
||||||
|
|
||||||
pipeline :ostatus do
|
pipeline :ostatus do
|
||||||
plug :accepts, ["xml", "atom", "html", "activity+json"]
|
plug(:accepts, ["xml", "atom", "html", "activity+json"])
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", Pleroma.Web do
|
scope "/", Pleroma.Web do
|
||||||
pipe_through :ostatus
|
pipe_through(:ostatus)
|
||||||
|
|
||||||
get "/objects/:uuid", OStatus.OStatusController, :object
|
get("/objects/:uuid", OStatus.OStatusController, :object)
|
||||||
get "/activities/:uuid", OStatus.OStatusController, :activity
|
get("/activities/:uuid", OStatus.OStatusController, :activity)
|
||||||
get "/notice/:id", OStatus.OStatusController, :notice
|
get("/notice/:id", OStatus.OStatusController, :notice)
|
||||||
get "/users/:nickname/feed", OStatus.OStatusController, :feed
|
get("/users/:nickname/feed", OStatus.OStatusController, :feed)
|
||||||
get "/users/:nickname", OStatus.OStatusController, :feed_redirect
|
get("/users/:nickname", OStatus.OStatusController, :feed_redirect)
|
||||||
|
|
||||||
if @federating do
|
if @federating do
|
||||||
post "/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming
|
post("/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming)
|
||||||
post "/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request
|
post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
|
||||||
get "/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation
|
get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation)
|
||||||
post "/push/subscriptions/:id", Websub.WebsubController, :websub_incoming
|
post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :activitypub do
|
pipeline :activitypub do
|
||||||
plug :accepts, ["activity+json"]
|
plug(:accepts, ["activity+json"])
|
||||||
plug Pleroma.Web.Plugs.HTTPSignaturePlug
|
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", Pleroma.Web.ActivityPub do
|
scope "/", Pleroma.Web.ActivityPub do
|
||||||
# XXX: not really ostatus
|
# XXX: not really ostatus
|
||||||
pipe_through :ostatus
|
pipe_through(:ostatus)
|
||||||
|
|
||||||
get "/users/:nickname/followers", ActivityPubController, :followers
|
get("/users/:nickname/followers", ActivityPubController, :followers)
|
||||||
get "/users/:nickname/following", ActivityPubController, :following
|
get("/users/:nickname/following", ActivityPubController, :following)
|
||||||
get "/users/:nickname/outbox", ActivityPubController, :outbox
|
get("/users/:nickname/outbox", ActivityPubController, :outbox)
|
||||||
end
|
end
|
||||||
|
|
||||||
if @federating do
|
if @federating do
|
||||||
scope "/", Pleroma.Web.ActivityPub do
|
scope "/", Pleroma.Web.ActivityPub do
|
||||||
pipe_through :activitypub
|
pipe_through(:activitypub)
|
||||||
post "/users/:nickname/inbox", ActivityPubController, :inbox
|
post("/users/:nickname/inbox", ActivityPubController, :inbox)
|
||||||
post "/inbox", ActivityPubController, :inbox
|
post("/inbox", ActivityPubController, :inbox)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/.well-known", Pleroma.Web do
|
scope "/.well-known", Pleroma.Web do
|
||||||
pipe_through :well_known
|
pipe_through(:well_known)
|
||||||
|
|
||||||
get "/host-meta", WebFinger.WebFingerController, :host_meta
|
get("/host-meta", WebFinger.WebFingerController, :host_meta)
|
||||||
get "/webfinger", WebFinger.WebFingerController, :webfinger
|
get("/webfinger", WebFinger.WebFingerController, :webfinger)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", Pleroma.Web.MastodonAPI do
|
scope "/", Pleroma.Web.MastodonAPI do
|
||||||
pipe_through :mastodon_html
|
pipe_through(:mastodon_html)
|
||||||
|
|
||||||
get "/web/login", MastodonAPIController, :login
|
get("/web/login", MastodonAPIController, :login)
|
||||||
post "/web/login", MastodonAPIController, :login_post
|
post("/web/login", MastodonAPIController, :login_post)
|
||||||
get "/web/*path", MastodonAPIController, :index
|
get("/web/*path", MastodonAPIController, :index)
|
||||||
delete "/auth/sign_out", MastodonAPIController, :logout
|
delete("/auth/sign_out", MastodonAPIController, :logout)
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :remote_media do
|
pipeline :remote_media do
|
||||||
plug :accepts, ["html"]
|
plug(:accepts, ["html"])
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/proxy/", Pleroma.Web.MediaProxy do
|
scope "/proxy/", Pleroma.Web.MediaProxy do
|
||||||
pipe_through :remote_media
|
pipe_through(:remote_media)
|
||||||
get "/:sig/:url", MediaProxyController, :remote
|
get("/:sig/:url", MediaProxyController, :remote)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", Fallback do
|
scope "/", Fallback do
|
||||||
get "/*path", RedirectController, :redirector
|
get("/*path", RedirectController, :redirector)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defmodule Fallback.RedirectController do
|
defmodule Fallback.RedirectController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
def redirector(conn, _params) do
|
def redirector(conn, _params) do
|
||||||
if Mix.env != :test do
|
if Mix.env() != :test do
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("text/html")
|
|> put_resp_content_type("text/html")
|
||||||
|> send_file(200, "priv/static/index.html")
|
|> send_file(200, "priv/static/index.html")
|
||||||
|
|
|
@ -38,9 +38,10 @@ def fetch_magic_key(salmon) do
|
||||||
def decode_and_validate(magickey, salmon) do
|
def decode_and_validate(magickey, salmon) do
|
||||||
[data, type, encoding, alg, sig] = decode(salmon)
|
[data, type, encoding, alg, sig] = decode(salmon)
|
||||||
|
|
||||||
signed_text = [data, type, encoding, alg]
|
signed_text =
|
||||||
|> Enum.map(&Base.url_encode64/1)
|
[data, type, encoding, alg]
|
||||||
|> Enum.join(".")
|
|> Enum.map(&Base.url_encode64/1)
|
||||||
|
|> Enum.join(".")
|
||||||
|
|
||||||
key = decode_key(magickey)
|
key = decode_key(magickey)
|
||||||
|
|
||||||
|
@ -54,22 +55,23 @@ def decode_and_validate(magickey, salmon) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def decode_key("RSA." <> magickey) do
|
def decode_key("RSA." <> magickey) do
|
||||||
make_integer = fn(bin) ->
|
make_integer = fn bin ->
|
||||||
list = :erlang.binary_to_list(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
|
end
|
||||||
|
|
||||||
[modulus, exponent] = magickey
|
[modulus, exponent] =
|
||||||
|> String.split(".")
|
magickey
|
||||||
|> Enum.map(fn (n) -> Base.url_decode64!(n, padding: false) end)
|
|> String.split(".")
|
||||||
|> Enum.map(make_integer)
|
|> Enum.map(fn n -> Base.url_decode64!(n, padding: false) end)
|
||||||
|
|> Enum.map(make_integer)
|
||||||
|
|
||||||
{:RSAPublicKey, modulus, exponent}
|
{:RSAPublicKey, modulus, exponent}
|
||||||
end
|
end
|
||||||
|
|
||||||
def encode_key({:RSAPublicKey, modulus, exponent}) do
|
def encode_key({:RSAPublicKey, modulus, exponent}) do
|
||||||
modulus_enc = :binary.encode_unsigned(modulus) |> Base.url_encode64
|
modulus_enc = :binary.encode_unsigned(modulus) |> Base.url_encode64()
|
||||||
exponent_enc = :binary.encode_unsigned(exponent) |> Base.url_encode64
|
exponent_enc = :binary.encode_unsigned(exponent) |> Base.url_encode64()
|
||||||
|
|
||||||
"RSA.#{modulus_enc}.#{exponent_enc}"
|
"RSA.#{modulus_enc}.#{exponent_enc}"
|
||||||
end
|
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.
|
# We try at compile time to generate natively an RSA key otherwise we fallback on the old way.
|
||||||
try do
|
try do
|
||||||
_ = :public_key.generate_key({:rsa, 2048, 65537})
|
_ = :public_key.generate_key({:rsa, 2048, 65537})
|
||||||
|
|
||||||
def generate_rsa_pem do
|
def generate_rsa_pem do
|
||||||
key = :public_key.generate_key({:rsa, 2048, 65537})
|
key = :public_key.generate_key({:rsa, 2048, 65537})
|
||||||
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
|
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}
|
{:ok, pem}
|
||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
_ ->
|
_ ->
|
||||||
def generate_rsa_pem do
|
def generate_rsa_pem do
|
||||||
port = Port.open({:spawn, "openssl genrsa"}, [:binary])
|
port = Port.open({:spawn, "openssl genrsa"}, [:binary])
|
||||||
{:ok, pem} = receive do
|
|
||||||
{^port, {:data, pem}} -> {:ok, pem}
|
{:ok, pem} =
|
||||||
end
|
receive do
|
||||||
|
{^port, {:data, pem}} -> {:ok, pem}
|
||||||
|
end
|
||||||
|
|
||||||
Port.close(port)
|
Port.close(port)
|
||||||
|
|
||||||
if Regex.match?(~r/RSA PRIVATE KEY/, pem) do
|
if Regex.match?(~r/RSA PRIVATE KEY/, pem) do
|
||||||
{:ok, pem}
|
{:ok, pem}
|
||||||
else
|
else
|
||||||
|
@ -113,17 +120,20 @@ def encode(private_key, doc) do
|
||||||
encoding = "base64url"
|
encoding = "base64url"
|
||||||
alg = "RSA-SHA256"
|
alg = "RSA-SHA256"
|
||||||
|
|
||||||
signed_text = [doc, type, encoding, alg]
|
signed_text =
|
||||||
|> Enum.map(&Base.url_encode64/1)
|
[doc, type, encoding, alg]
|
||||||
|> Enum.join(".")
|
|> Enum.map(&Base.url_encode64/1)
|
||||||
|
|> Enum.join(".")
|
||||||
|
|
||||||
signature = signed_text
|
signature =
|
||||||
|> :public_key.sign(:sha256, private_key)
|
signed_text
|
||||||
|> to_string
|
|> :public_key.sign(:sha256, private_key)
|
||||||
|> Base.url_encode64
|
|> to_string
|
||||||
|
|> Base.url_encode64()
|
||||||
|
|
||||||
doc_base64 = doc
|
doc_base64 =
|
||||||
|> Base.url_encode64
|
doc
|
||||||
|
|> Base.url_encode64()
|
||||||
|
|
||||||
# Don't need proper xml building, these strings are safe to leave unescaped
|
# Don't need proper xml building, these strings are safe to leave unescaped
|
||||||
salmon = """
|
salmon = """
|
||||||
|
@ -141,20 +151,29 @@ def encode(private_key, doc) do
|
||||||
|
|
||||||
def remote_users(%{data: %{"to" => to} = data}) do
|
def remote_users(%{data: %{"to" => to} = data}) do
|
||||||
to = to ++ (data["cc"] || [])
|
to = to ++ (data["cc"] || [])
|
||||||
|
|
||||||
to
|
to
|
||||||
|> Enum.map(fn(id) -> User.get_cached_by_ap_id(id) end)
|
|> Enum.map(fn id -> User.get_cached_by_ap_id(id) end)
|
||||||
|> Enum.filter(fn(user) -> user && !user.local end)
|
|> Enum.filter(fn user -> user && !user.local end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp send_to_user(%{info: %{"salmon" => salmon}}, feed, poster) do
|
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)
|
Logger.debug(fn -> "Pushed to #{salmon}, code #{code}" end)
|
||||||
else
|
else
|
||||||
e -> Logger.debug(fn -> "Pushing Salmon to #{salmon} failed, #{inspect(e)}" end)
|
e -> Logger.debug(fn -> "Pushing Salmon to #{salmon} failed, #{inspect(e)}" end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp send_to_user(_,_,_), do: nil
|
defp send_to_user(_, _, _), do: nil
|
||||||
|
|
||||||
@supported_activities [
|
@supported_activities [
|
||||||
"Create",
|
"Create",
|
||||||
|
@ -165,18 +184,21 @@ defp send_to_user(_,_,_), do: nil
|
||||||
"Delete"
|
"Delete"
|
||||||
]
|
]
|
||||||
def publish(user, activity, poster \\ &@httpoison.post/4)
|
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)
|
def publish(%{info: %{"keys" => keys}} = user, %{data: %{"type" => type}} = activity, poster)
|
||||||
|> ActivityRepresenter.wrap_with_entry
|
when type in @supported_activities do
|
||||||
|> :xmerl.export_simple(:xmerl_xml)
|
feed =
|
||||||
|> to_string
|
ActivityRepresenter.to_simple_form(activity, user, true)
|
||||||
|
|> ActivityRepresenter.wrap_with_entry()
|
||||||
|
|> :xmerl.export_simple(:xmerl_xml)
|
||||||
|
|> to_string
|
||||||
|
|
||||||
if feed do
|
if feed do
|
||||||
{:ok, private, _} = keys_from_pem(keys)
|
{:ok, private, _} = keys_from_pem(keys)
|
||||||
{:ok, feed} = encode(private, feed)
|
{:ok, feed} = encode(private, feed)
|
||||||
|
|
||||||
remote_users(activity)
|
remote_users(activity)
|
||||||
|> Enum.each(fn(remote_user) ->
|
|> Enum.each(fn remote_user ->
|
||||||
Task.start(fn ->
|
Task.start(fn ->
|
||||||
Logger.debug(fn -> "Sending Salmon to #{remote_user.ap_id}" end)
|
Logger.debug(fn -> "Sending Salmon to #{remote_user.ap_id}" end)
|
||||||
send_to_user(remote_user, feed, poster)
|
send_to_user(remote_user, feed, poster)
|
||||||
|
|
|
@ -5,9 +5,11 @@ defmodule Pleroma.Web.Streamer do
|
||||||
|
|
||||||
def start_link do
|
def start_link do
|
||||||
spawn(fn ->
|
spawn(fn ->
|
||||||
Process.sleep(1000 * 30) # 30 seconds
|
# 30 seconds
|
||||||
|
Process.sleep(1000 * 30)
|
||||||
GenServer.cast(__MODULE__, %{action: :ping})
|
GenServer.cast(__MODULE__, %{action: :ping})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
|
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -25,39 +27,54 @@ def stream(topic, item) do
|
||||||
|
|
||||||
def handle_cast(%{action: :ping}, topics) do
|
def handle_cast(%{action: :ping}, topics) do
|
||||||
Map.values(topics)
|
Map.values(topics)
|
||||||
|> List.flatten
|
|> List.flatten()
|
||||||
|> Enum.each(fn (socket) ->
|
|> Enum.each(fn socket ->
|
||||||
Logger.debug("Sending keepalive ping")
|
Logger.debug("Sending keepalive ping")
|
||||||
send socket.transport_pid, {:text, ""}
|
send(socket.transport_pid, {:text, ""})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
spawn(fn ->
|
spawn(fn ->
|
||||||
Process.sleep(1000 * 30) # 30 seconds
|
# 30 seconds
|
||||||
|
Process.sleep(1000 * 30)
|
||||||
GenServer.cast(__MODULE__, %{action: :ping})
|
GenServer.cast(__MODULE__, %{action: :ping})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
{:noreply, topics}
|
{:noreply, topics}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_cast(%{action: :stream, topic: "user", item: %Notification{} = item}, topics) do
|
def handle_cast(%{action: :stream, topic: "user", item: %Notification{} = item}, topics) do
|
||||||
topic = "user:#{item.user_id}"
|
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)
|
end)
|
||||||
|
|
||||||
{:noreply, topics}
|
{:noreply, topics}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_cast(%{action: :stream, topic: "user", item: item}, topics) do
|
def handle_cast(%{action: :stream, topic: "user", item: item}, topics) do
|
||||||
Logger.debug("Trying to push to users")
|
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)
|
push_to_socket(topics, topic, item)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
{:noreply, topics}
|
{:noreply, topics}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -92,13 +109,21 @@ def handle_cast(m, state) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def push_to_socket(topics, topic, item) do
|
def push_to_socket(topics, topic, item) do
|
||||||
Enum.each(topics[topic] || [], fn (socket) ->
|
Enum.each(topics[topic] || [], fn socket ->
|
||||||
json = %{
|
json =
|
||||||
event: "update",
|
%{
|
||||||
payload: Pleroma.Web.MastodonAPI.StatusView.render("status.json", activity: item, for: socket.assigns[:user]) |> Jason.encode!
|
event: "update",
|
||||||
} |> Jason.encode!
|
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)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -11,21 +11,21 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||||
|
|
||||||
def show_password_reset(conn, %{"token" => token}) do
|
def show_password_reset(conn, %{"token" => token}) do
|
||||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||||
%User{} = user <- Repo.get(User, token.user_id) do
|
%User{} = user <- Repo.get(User, token.user_id) do
|
||||||
render conn, "password_reset.html", %{
|
render(conn, "password_reset.html", %{
|
||||||
token: token,
|
token: token,
|
||||||
user: user
|
user: user
|
||||||
}
|
})
|
||||||
else
|
else
|
||||||
_e -> render conn, "invalid_token.html"
|
_e -> render(conn, "invalid_token.html")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def password_reset(conn, %{"data" => data}) do
|
def password_reset(conn, %{"data" => data}) do
|
||||||
with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do
|
with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do
|
||||||
render conn, "password_reset_success.html"
|
render(conn, "password_reset_success.html")
|
||||||
else
|
else
|
||||||
_e -> render conn, "password_reset_failed.html"
|
_e -> render(conn, "password_reset_failed.html")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -34,14 +34,19 @@ def help_test(conn, _params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
|
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
|
||||||
with %User{} = user <- User.get_cached_by_nickname(nick),
|
with %User{} = user <- User.get_cached_by_nickname(nick), avatar = User.avatar_url(user) do
|
||||||
avatar = User.avatar_url(user) do
|
|
||||||
conn
|
conn
|
||||||
|> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
|
|> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
|
||||||
else
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do
|
def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do
|
||||||
with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
|
with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
|
||||||
%User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do
|
%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))
|
|> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id))
|
||||||
else
|
else
|
||||||
_e ->
|
_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
|
||||||
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})
|
|> render("follow.html", %{error: err, acct: acct, avatar: avatar, name: name, id: id})
|
||||||
else
|
else
|
||||||
conn
|
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
|
||||||
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)
|
followee = Repo.get(User, id)
|
||||||
avatar = User.avatar_url(followee)
|
avatar = User.avatar_url(followee)
|
||||||
name = followee.nickname
|
name = followee.nickname
|
||||||
|
|
||||||
with %User{} = user <- User.get_cached_by_nickname(username),
|
with %User{} = user <- User.get_cached_by_nickname(username),
|
||||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
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, follower} <- User.follow(user, followee),
|
||||||
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
||||||
conn
|
conn
|
||||||
|
@ -82,9 +100,15 @@ def do_remote_follow(conn, %{"authorization" => %{"name" => username, "password"
|
||||||
else
|
else
|
||||||
_e ->
|
_e ->
|
||||||
conn
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
|
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
|
||||||
with %User{} = followee <- Repo.get(User, id),
|
with %User{} = followee <- Repo.get(User, id),
|
||||||
{:ok, follower} <- User.follow(user, followee),
|
{: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})
|
|> render("followed.html", %{error: false})
|
||||||
else
|
else
|
||||||
e ->
|
e ->
|
||||||
Logger.debug("Remote follow failed with error #{inspect e}")
|
Logger.debug("Remote follow failed with error #{inspect(e)}")
|
||||||
conn
|
|
||||||
|> render("followed.html", %{error: inspect(e)})
|
conn
|
||||||
|
|> render("followed.html", %{error: inspect(e)})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -107,60 +132,67 @@ def config(conn, _params) do
|
||||||
<config>
|
<config>
|
||||||
<site>
|
<site>
|
||||||
<name>#{Keyword.get(@instance, :name)}</name>
|
<name>#{Keyword.get(@instance, :name)}</name>
|
||||||
<site>#{Web.base_url}</site>
|
<site>#{Web.base_url()}</site>
|
||||||
<textlimit>#{Keyword.get(@instance, :limit)}</textlimit>
|
<textlimit>#{Keyword.get(@instance, :limit)}</textlimit>
|
||||||
<closed>#{!Keyword.get(@instance, :registrations_open)}</closed>
|
<closed>#{!Keyword.get(@instance, :registrations_open)}</closed>
|
||||||
</site>
|
</site>
|
||||||
</config>
|
</config>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/xml")
|
|> put_resp_content_type("application/xml")
|
||||||
|> send_resp(200, response)
|
|> send_resp(200, response)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
json(conn, %{
|
json(conn, %{
|
||||||
site: %{
|
site: %{
|
||||||
name: Keyword.get(@instance, :name),
|
name: Keyword.get(@instance, :name),
|
||||||
server: Web.base_url,
|
server: Web.base_url(),
|
||||||
textlimit: to_string(Keyword.get(@instance, :limit)),
|
textlimit: to_string(Keyword.get(@instance, :limit)),
|
||||||
closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1")
|
closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def version(conn, _params) do
|
def version(conn, _params) do
|
||||||
version = Keyword.get(@instance, :version)
|
version = Keyword.get(@instance, :version)
|
||||||
|
|
||||||
case get_format(conn) do
|
case get_format(conn) do
|
||||||
"xml" ->
|
"xml" ->
|
||||||
response = "<version>#{version}</version>"
|
response = "<version>#{version}</version>"
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/xml")
|
|> put_resp_content_type("application/xml")
|
||||||
|> send_resp(200, response)
|
|> send_resp(200, response)
|
||||||
_ -> json(conn, version)
|
|
||||||
|
_ ->
|
||||||
|
json(conn, version)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def emoji(conn, _params) do
|
def emoji(conn, _params) do
|
||||||
json conn, Enum.into(Formatter.get_custom_emoji(), %{})
|
json(conn, Enum.into(Formatter.get_custom_emoji(), %{}))
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
|
def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
|
||||||
follow_import(conn, %{"list" => File.read!(listfile.path)})
|
follow_import(conn, %{"list" => File.read!(listfile.path)})
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow_import(%{assigns: %{user: user}} = conn, %{"list" => list}) do
|
def follow_import(%{assigns: %{user: user}} = conn, %{"list" => list}) do
|
||||||
Task.start(fn ->
|
Task.start(fn ->
|
||||||
String.split(list)
|
String.split(list)
|
||||||
|> Enum.map(fn nick ->
|
|> Enum.map(fn nick ->
|
||||||
with %User{} = follower <- User.get_cached_by_ap_id(user.ap_id),
|
with %User{} = follower <- User.get_cached_by_ap_id(user.ap_id),
|
||||||
%User{} = followed <- User.get_or_fetch_by_nickname(nick),
|
%User{} = followed <- User.get_or_fetch_by_nickname(nick),
|
||||||
{:ok, follower} <- User.follow(follower, followed) do
|
{:ok, follower} <- User.follow(follower, followed) do
|
||||||
ActivityPub.follow(follower, followed)
|
ActivityPub.follow(follower, followed)
|
||||||
else
|
else
|
||||||
_e -> Logger.debug "follow_import: following #{nick} failed"
|
_e -> Logger.debug("follow_import: following #{nick} failed")
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
json conn, "job started"
|
json(conn, "job started")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,18 +7,22 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
|
||||||
alias Pleroma.Formatter
|
alias Pleroma.Formatter
|
||||||
|
|
||||||
defp user_by_ap_id(user_list, ap_id) do
|
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
|
end
|
||||||
|
|
||||||
def to_map(%Activity{data: %{"type" => "Announce", "actor" => actor, "published" => created_at}} = activity,
|
def to_map(
|
||||||
%{users: users, announced_activity: announced_activity} = opts) do
|
%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)
|
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."
|
text = "#{user.nickname} retweeted a status."
|
||||||
|
|
||||||
announced_user = user_by_ap_id(users, announced_activity.data["actor"])
|
announced_user = user_by_ap_id(users, announced_activity.data["actor"])
|
||||||
retweeted_status = to_map(announced_activity, Map.merge(%{user: announced_user}, opts))
|
retweeted_status = to_map(announced_activity, Map.merge(%{user: announced_user}, opts))
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"id" => activity.id,
|
"id" => activity.id,
|
||||||
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
|
"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
|
end
|
||||||
|
|
||||||
def to_map(%Activity{data: %{"type" => "Like", "published" => created_at}} = activity,
|
def to_map(
|
||||||
%{user: user, liked_activity: liked_activity} = opts) do
|
%Activity{data: %{"type" => "Like", "published" => created_at}} = activity,
|
||||||
created_at = created_at |> Utils.date_to_asctime
|
%{user: user, liked_activity: liked_activity} = opts
|
||||||
|
) do
|
||||||
|
created_at = created_at |> Utils.date_to_asctime()
|
||||||
|
|
||||||
text = "#{user.nickname} favorited a status."
|
text = "#{user.nickname} favorited a status."
|
||||||
|
|
||||||
|
@ -56,12 +62,16 @@ def to_map(%Activity{data: %{"type" => "Like", "published" => created_at}} = act
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_map(%Activity{data: %{"type" => "Follow", "object" => followed_id}} = activity, %{user: user} = opts) do
|
def to_map(
|
||||||
created_at = activity.data["published"] || (DateTime.to_iso8601(activity.inserted_at))
|
%Activity{data: %{"type" => "Follow", "object" => followed_id}} = activity,
|
||||||
created_at = created_at |> Utils.date_to_asctime
|
%{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)
|
followed = User.get_cached_by_ap_id(followed_id)
|
||||||
text = "#{user.nickname} started following #{followed.nickname}"
|
text = "#{user.nickname} started following #{followed.nickname}"
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"id" => activity.id,
|
"id" => activity.id,
|
||||||
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
|
"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:
|
# TODO:
|
||||||
# Make this more proper. Just a placeholder to not break the frontend.
|
# 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
|
def to_map(
|
||||||
created_at = created_at |> Utils.date_to_asctime
|
%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}"
|
text = "#{user.nickname} undid the action at #{undid_activity}"
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"id" => activity.id,
|
"id" => activity.id,
|
||||||
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
|
"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
|
end
|
||||||
|
|
||||||
def to_map(%Activity{data: %{"type" => "Delete", "published" => created_at, "object" => _ }} = activity, %{user: user} = opts) do
|
def to_map(
|
||||||
created_at = created_at |> Utils.date_to_asctime
|
%Activity{data: %{"type" => "Delete", "published" => created_at, "object" => _}} =
|
||||||
|
activity,
|
||||||
|
%{user: user} = opts
|
||||||
|
) do
|
||||||
|
created_at = created_at |> Utils.date_to_asctime()
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"id" => activity.id,
|
"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]}),
|
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
|
||||||
"attentions" => [],
|
"attentions" => [],
|
||||||
"statusnet_html" => "deleted notice {{tag",
|
"statusnet_html" => "deleted notice {{tag",
|
||||||
"text" => "deleted notice {{tag" ,
|
"text" => "deleted notice {{tag",
|
||||||
"is_local" => activity.local,
|
"is_local" => activity.local,
|
||||||
"is_post_verb" => false,
|
"is_post_verb" => false,
|
||||||
"created_at" => created_at,
|
"created_at" => created_at,
|
||||||
|
@ -117,8 +137,11 @@ def to_map(%Activity{data: %{"type" => "Delete", "published" => created_at, "obj
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_map(%Activity{data: %{"object" => %{"content" => content} = object}} = activity, %{user: user} = opts) do
|
def to_map(
|
||||||
created_at = object["published"] |> Utils.date_to_asctime
|
%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
|
like_count = object["like_count"] || 0
|
||||||
announcement_count = object["announcement_count"] || 0
|
announcement_count = object["announcement_count"] || 0
|
||||||
favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
|
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] || []
|
mentions = opts[:mentioned] || []
|
||||||
|
|
||||||
attentions = activity.recipients
|
attentions =
|
||||||
|> Enum.map(fn (ap_id) -> Enum.find(mentions, fn(user) -> ap_id == user.ap_id end) end)
|
activity.recipients
|
||||||
|> Enum.filter(&(&1))
|
|> Enum.map(fn ap_id -> Enum.find(mentions, fn user -> ap_id == user.ap_id end) end)
|
||||||
|> Enum.map(fn (user) -> UserView.render("show.json", %{user: user, for: opts[:for]}) end)
|
|> Enum.filter(& &1)
|
||||||
|
|> Enum.map(fn user -> UserView.render("show.json", %{user: user, for: opts[:for]}) end)
|
||||||
|
|
||||||
conversation_id = conversation_id(activity)
|
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
|
tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags
|
||||||
|
|
||||||
summary = activity.data["object"]["summary"]
|
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)
|
content =
|
||||||
|> Formatter.emojify(object["emoji"])
|
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,
|
"id" => activity.id,
|
||||||
|
@ -175,7 +202,8 @@ def to_map(%Activity{data: %{"object" => %{"content" => content} = object}} = ac
|
||||||
def conversation_id(activity) do
|
def conversation_id(activity) do
|
||||||
with context when not is_nil(context) <- activity.data["context"] do
|
with context when not is_nil(context) <- activity.data["context"] do
|
||||||
TwitterAPI.context_to_conversation_id(context)
|
TwitterAPI.context_to_conversation_id(context)
|
||||||
else _e -> nil
|
else
|
||||||
|
_e -> nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
defmodule Pleroma.Web.TwitterAPI.Representers.BaseRepresenter do
|
defmodule Pleroma.Web.TwitterAPI.Representers.BaseRepresenter do
|
||||||
defmacro __using__(_opts) do
|
defmacro __using__(_opts) do
|
||||||
quote 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
|
def to_json(object, options) do
|
||||||
object
|
object
|
||||||
|> to_map(options)
|
|> to_map(options)
|
||||||
|> Jason.encode!
|
|> Jason.encode!()
|
||||||
end
|
end
|
||||||
|
|
||||||
def enum_to_list(enum, options) do
|
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)
|
Enum.map(enum, mapping)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -17,11 +20,14 @@ def to_map(object) do
|
||||||
to_map(object, %{})
|
to_map(object, %{})
|
||||||
end
|
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
|
def enum_to_json(enum, options) do
|
||||||
enum
|
enum
|
||||||
|> enum_to_list(options)
|
|> enum_to_list(options)
|
||||||
|> Jason.encode!
|
|> Jason.encode!()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter do
|
||||||
|
|
||||||
def to_map(%Object{data: %{"url" => [url | _]}} = object, _opts) do
|
def to_map(%Object{data: %{"url" => [url | _]}} = object, _opts) do
|
||||||
data = object.data
|
data = object.data
|
||||||
|
|
||||||
%{
|
%{
|
||||||
url: url["href"] |> Pleroma.Web.MediaProxy.url(),
|
url: url["href"] |> Pleroma.Web.MediaProxy.url(),
|
||||||
mimetype: url["mediaType"],
|
mimetype: url["mediaType"],
|
||||||
|
|
|
@ -13,37 +13,42 @@ def create_status(%User{} = user, %{"status" => _} = data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_friend_statuses(user, opts \\ %{}) do
|
def fetch_friend_statuses(user, opts \\ %{}) do
|
||||||
opts = opts
|
opts =
|
||||||
|> Map.put("blocking_user", user)
|
opts
|
||||||
|> Map.put("user", user)
|
|> Map.put("blocking_user", user)
|
||||||
|> Map.put("type", ["Create", "Announce", "Follow", "Like"])
|
|> Map.put("user", user)
|
||||||
|
|> Map.put("type", ["Create", "Announce", "Follow", "Like"])
|
||||||
|
|
||||||
ActivityPub.fetch_activities([user.ap_id | user.following], opts)
|
ActivityPub.fetch_activities([user.ap_id | user.following], opts)
|
||||||
|> activities_to_statuses(%{for: user})
|
|> activities_to_statuses(%{for: user})
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_public_statuses(user, opts \\ %{}) do
|
def fetch_public_statuses(user, opts \\ %{}) do
|
||||||
opts = opts
|
opts =
|
||||||
|> Map.put("local_only", true)
|
opts
|
||||||
|> Map.put("blocking_user", user)
|
|> Map.put("local_only", true)
|
||||||
|> Map.put("type", ["Create", "Announce", "Follow"])
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("type", ["Create", "Announce", "Follow"])
|
||||||
|
|
||||||
ActivityPub.fetch_public_activities(opts)
|
ActivityPub.fetch_public_activities(opts)
|
||||||
|> activities_to_statuses(%{for: user})
|
|> activities_to_statuses(%{for: user})
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_public_and_external_statuses(user, opts \\ %{}) do
|
def fetch_public_and_external_statuses(user, opts \\ %{}) do
|
||||||
opts = opts
|
opts =
|
||||||
|> Map.put("blocking_user", user)
|
opts
|
||||||
|> Map.put("type", ["Create", "Announce", "Follow"])
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("type", ["Create", "Announce", "Follow"])
|
||||||
|
|
||||||
ActivityPub.fetch_public_activities(opts)
|
ActivityPub.fetch_public_activities(opts)
|
||||||
|> activities_to_statuses(%{for: user})
|
|> activities_to_statuses(%{for: user})
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_user_statuses(user, opts \\ %{}) do
|
def fetch_user_statuses(user, opts \\ %{}) do
|
||||||
opts = opts
|
opts =
|
||||||
|> Map.put("type", ["Create"])
|
opts
|
||||||
|
|> Map.put("type", ["Create"])
|
||||||
|
|
||||||
ActivityPub.fetch_public_activities(opts)
|
ActivityPub.fetch_public_activities(opts)
|
||||||
|> activities_to_statuses(%{for: user})
|
|> activities_to_statuses(%{for: user})
|
||||||
end
|
end
|
||||||
|
@ -55,12 +60,16 @@ def fetch_mentions(user, opts \\ %{}) do
|
||||||
|
|
||||||
def fetch_conversation(user, id) do
|
def fetch_conversation(user, id) do
|
||||||
with context when is_binary(context) <- conversation_id_to_context(id),
|
with context when is_binary(context) <- conversation_id_to_context(id),
|
||||||
activities <- ActivityPub.fetch_activities_for_context(context, %{"blocking_user" => user, "user" => user}),
|
activities <-
|
||||||
statuses <- activities |> activities_to_statuses(%{for: user})
|
ActivityPub.fetch_activities_for_context(context, %{
|
||||||
do
|
"blocking_user" => user,
|
||||||
|
"user" => user
|
||||||
|
}),
|
||||||
|
statuses <- activities |> activities_to_statuses(%{for: user}) do
|
||||||
statuses
|
statuses
|
||||||
else _e ->
|
else
|
||||||
[]
|
_e ->
|
||||||
|
[]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -74,8 +83,7 @@ def fetch_status(user, id) do
|
||||||
def follow(%User{} = follower, params) do
|
def follow(%User{} = follower, params) do
|
||||||
with {:ok, %User{} = followed} <- get_user(params),
|
with {:ok, %User{} = followed} <- get_user(params),
|
||||||
{:ok, follower} <- User.follow(follower, followed),
|
{:ok, follower} <- User.follow(follower, followed),
|
||||||
{:ok, activity} <- ActivityPub.follow(follower, followed)
|
{:ok, activity} <- ActivityPub.follow(follower, followed) do
|
||||||
do
|
|
||||||
{:ok, follower, followed, activity}
|
{:ok, follower, followed, activity}
|
||||||
else
|
else
|
||||||
err -> err
|
err -> err
|
||||||
|
@ -83,16 +91,17 @@ def follow(%User{} = follower, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def unfollow(%User{} = follower, params) do
|
def unfollow(%User{} = follower, params) do
|
||||||
with { :ok, %User{} = unfollowed } <- get_user(params),
|
with {:ok, %User{} = unfollowed} <- get_user(params),
|
||||||
{ :ok, follower, follow_activity } <- User.unfollow(follower, unfollowed),
|
{:ok, follower, follow_activity} <- User.unfollow(follower, unfollowed),
|
||||||
{ :ok, _activity } <- ActivityPub.insert(%{
|
{:ok, _activity} <-
|
||||||
"type" => "Undo",
|
ActivityPub.insert(%{
|
||||||
"actor" => follower.ap_id,
|
"type" => "Undo",
|
||||||
"object" => follow_activity.data["id"], # get latest Follow for these users
|
"actor" => follower.ap_id,
|
||||||
"published" => make_date()
|
# get latest Follow for these users
|
||||||
})
|
"object" => follow_activity.data["id"],
|
||||||
do
|
"published" => make_date()
|
||||||
{ :ok, follower, unfollowed }
|
}) do
|
||||||
|
{:ok, follower, unfollowed}
|
||||||
else
|
else
|
||||||
err -> err
|
err -> err
|
||||||
end
|
end
|
||||||
|
@ -100,8 +109,7 @@ def unfollow(%User{} = follower, params) do
|
||||||
|
|
||||||
def block(%User{} = blocker, params) do
|
def block(%User{} = blocker, params) do
|
||||||
with {:ok, %User{} = blocked} <- get_user(params),
|
with {:ok, %User{} = blocked} <- get_user(params),
|
||||||
{:ok, blocker} <- User.block(blocker, blocked)
|
{:ok, blocker} <- User.block(blocker, blocked) do
|
||||||
do
|
|
||||||
{:ok, blocker, blocked}
|
{:ok, blocker, blocked}
|
||||||
else
|
else
|
||||||
err -> err
|
err -> err
|
||||||
|
@ -110,8 +118,7 @@ def block(%User{} = blocker, params) do
|
||||||
|
|
||||||
def unblock(%User{} = blocker, params) do
|
def unblock(%User{} = blocker, params) do
|
||||||
with {:ok, %User{} = blocked} <- get_user(params),
|
with {:ok, %User{} = blocked} <- get_user(params),
|
||||||
{:ok, blocker} <- User.unblock(blocker, blocked)
|
{:ok, blocker} <- User.unblock(blocker, blocked) do
|
||||||
do
|
|
||||||
{:ok, blocker, blocked}
|
{:ok, blocker, blocked}
|
||||||
else
|
else
|
||||||
err -> err
|
err -> err
|
||||||
|
@ -163,13 +170,15 @@ def upload(%Plug.Upload{} = file, format \\ "xml") do
|
||||||
<atom:link rel="enclosure" href="#{href}" type="#{type}"></atom:link>
|
<atom:link rel="enclosure" href="#{href}" type="#{type}"></atom:link>
|
||||||
</rsp>
|
</rsp>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"json" ->
|
"json" ->
|
||||||
%{
|
%{
|
||||||
media_id: object.id,
|
media_id: object.id,
|
||||||
media_id_string: "#{object.id}}",
|
media_id_string: "#{object.id}}",
|
||||||
media_url: href,
|
media_url: href,
|
||||||
size: 0
|
size: 0
|
||||||
} |> Jason.encode!
|
}
|
||||||
|
|> Jason.encode!()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -189,9 +198,11 @@ def register_user(params) do
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
{:error, changeset} ->
|
{:error, changeset} ->
|
||||||
errors = Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
|
errors =
|
||||||
|> Jason.encode!
|
Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
|
||||||
{:error, %{error: errors}}
|
|> Jason.encode!()
|
||||||
|
|
||||||
|
{:error, %{error: errors}}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -209,16 +220,20 @@ def get_user(user \\ nil, params) do
|
||||||
case target = get_by_id_or_nickname(user_id) do
|
case target = get_by_id_or_nickname(user_id) do
|
||||||
nil ->
|
nil ->
|
||||||
{:error, "No user with such user_id"}
|
{:error, "No user with such user_id"}
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
{:ok, target}
|
{:ok, target}
|
||||||
end
|
end
|
||||||
|
|
||||||
%{"screen_name" => nickname} ->
|
%{"screen_name" => nickname} ->
|
||||||
case target = Repo.get_by(User, nickname: nickname) do
|
case target = Repo.get_by(User, nickname: nickname) do
|
||||||
nil ->
|
nil ->
|
||||||
{:error, "No user with such screen_name"}
|
{:error, "No user with such screen_name"}
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
{:ok, target}
|
{:ok, target}
|
||||||
end
|
end
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
if user do
|
if user do
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
|
@ -229,6 +244,7 @@ def get_user(user \\ nil, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_int(string, default)
|
defp parse_int(string, default)
|
||||||
|
|
||||||
defp parse_int(string, default) when is_binary(string) do
|
defp parse_int(string, default) when is_binary(string) do
|
||||||
with {n, _} <- Integer.parse(string) do
|
with {n, _} <- Integer.parse(string) do
|
||||||
n
|
n
|
||||||
|
@ -236,6 +252,7 @@ defp parse_int(string, default) when is_binary(string) do
|
||||||
_e -> default
|
_e -> default
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_int(_, default), do: default
|
defp parse_int(_, default), do: default
|
||||||
|
|
||||||
def search(user, %{"q" => query} = params) do
|
def search(user, %{"q" => query} = params) do
|
||||||
|
@ -243,19 +260,28 @@ def search(user, %{"q" => query} = params) do
|
||||||
page = parse_int(params["page"], 1)
|
page = parse_int(params["page"], 1)
|
||||||
offset = (page - 1) * limit
|
offset = (page - 1) * limit
|
||||||
|
|
||||||
q = from a in Activity,
|
q =
|
||||||
where: fragment("?->>'type' = 'Create'", a.data),
|
from(
|
||||||
where: fragment("to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)", a.data, ^query),
|
a in Activity,
|
||||||
limit: ^limit,
|
where: fragment("?->>'type' = 'Create'", a.data),
|
||||||
offset: ^offset,
|
where:
|
||||||
order_by: [desc: :inserted_at] # this one isn't indexed so psql won't take the wrong index.
|
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 = Repo.all(q)
|
||||||
activities_to_statuses(activities, %{for: user})
|
activities_to_statuses(activities, %{for: user})
|
||||||
end
|
end
|
||||||
|
|
||||||
defp activities_to_statuses(activities, opts) do
|
defp activities_to_statuses(activities, opts) do
|
||||||
Enum.map(activities, fn(activity) ->
|
Enum.map(activities, fn activity ->
|
||||||
activity_to_status(activity, opts)
|
activity_to_status(activity, opts)
|
||||||
end)
|
end)
|
||||||
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)
|
user = User.get_cached_by_ap_id(actor)
|
||||||
[liked_activity] = Activity.all_by_object_ap_id(activity.data["object"])
|
[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
|
end
|
||||||
|
|
||||||
# For announces, fetch the announced activity and the user.
|
# 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_activity] = Activity.all_by_object_ap_id(activity.data["object"])
|
||||||
announced_actor = User.get_cached_by_ap_id(announced_activity.data["actor"])
|
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
|
end
|
||||||
|
|
||||||
defp activity_to_status(%Activity{data: %{"type" => "Delete"}} = activity, opts) do
|
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"])
|
actor = get_in(activity.data, ["actor"])
|
||||||
user = User.get_cached_by_ap_id(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 = Repo.all(from user in User, where: user.ap_id in ^activity.data["to"])
|
||||||
mentioned_users = Enum.map(activity.recipients || [], fn (ap_id) ->
|
mentioned_users =
|
||||||
if ap_id do
|
Enum.map(activity.recipients || [], fn ap_id ->
|
||||||
User.get_cached_by_ap_id(ap_id)
|
if ap_id do
|
||||||
else
|
User.get_cached_by_ap_id(ap_id)
|
||||||
nil
|
else
|
||||||
end
|
nil
|
||||||
end)
|
end
|
||||||
|> Enum.filter(&(&1))
|
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
|
end
|
||||||
|
|
||||||
defp make_date do
|
defp make_date do
|
||||||
DateTime.utc_now() |> DateTime.to_iso8601
|
DateTime.utc_now() |> DateTime.to_iso8601()
|
||||||
end
|
end
|
||||||
|
|
||||||
def context_to_conversation_id(context) do
|
def context_to_conversation_id(context) do
|
||||||
with %Object{id: id} <- Object.get_cached_by_ap_id(context) do
|
with %Object{id: id} <- Object.get_cached_by_ap_id(context) do
|
||||||
id
|
id
|
||||||
else _e ->
|
else
|
||||||
|
_e ->
|
||||||
changeset = Object.context_mapping(context)
|
changeset = Object.context_mapping(context)
|
||||||
|
|
||||||
case Repo.insert(changeset) do
|
case Repo.insert(changeset) do
|
||||||
{:ok, %{id: id}} -> id
|
{:ok, %{id: id}} ->
|
||||||
|
id
|
||||||
|
|
||||||
# This should be solved by an upsert, but it seems ecto
|
# This should be solved by an upsert, but it seems ecto
|
||||||
# has problems accessing the constraint inside the jsonb.
|
# 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
|
end
|
||||||
end
|
end
|
||||||
|
@ -322,8 +363,9 @@ def context_to_conversation_id(context) do
|
||||||
def conversation_id_to_context(id) do
|
def conversation_id_to_context(id) do
|
||||||
with %Object{data: %{"id" => context}} <- Repo.get(Object, id) do
|
with %Object{data: %{"id" => context}} <- Repo.get(Object, id) do
|
||||||
context
|
context
|
||||||
else _e ->
|
else
|
||||||
{:error, "No such conversation"}
|
_e ->
|
||||||
|
{:error, "No such conversation"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -331,12 +373,15 @@ def get_external_profile(for_user, uri) do
|
||||||
with %User{} = user <- User.get_or_fetch(uri) do
|
with %User{} = user <- User.get_or_fetch(uri) do
|
||||||
spawn(fn ->
|
spawn(fn ->
|
||||||
with url <- user.info["topic"],
|
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)
|
OStatus.handle_incoming(body)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
{:ok, UserView.render("show.json", %{user: user, for: for_user})}
|
{:ok, UserView.render("show.json", %{user: user, for: for_user})}
|
||||||
else _e ->
|
else
|
||||||
|
_e ->
|
||||||
{:error, "Couldn't find user"}
|
{:error, "Couldn't find user"}
|
||||||
end
|
end
|
||||||
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
|
def status_update(%{assigns: %{user: user}} = conn, %{"status" => _} = status_data) do
|
||||||
with media_ids <- extract_media_ids(status_data),
|
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
|
conn
|
||||||
|> json(ActivityRepresenter.to_map(activity, %{user: user}))
|
|> json(ActivityRepresenter.to_map(activity, %{user: user}))
|
||||||
else
|
else
|
||||||
|
@ -35,10 +36,10 @@ defp empty_status_reply(conn) do
|
||||||
defp extract_media_ids(status_data) do
|
defp extract_media_ids(status_data) do
|
||||||
with media_ids when not is_nil(media_ids) <- status_data["media_ids"],
|
with media_ids when not is_nil(media_ids) <- status_data["media_ids"],
|
||||||
split_ids <- String.split(media_ids, ","),
|
split_ids <- String.split(media_ids, ","),
|
||||||
clean_ids <- Enum.reject(split_ids, fn (id) -> String.length(id) == 0 end)
|
clean_ids <- Enum.reject(split_ids, fn id -> String.length(id) == 0 end) do
|
||||||
do
|
clean_ids
|
||||||
clean_ids
|
else
|
||||||
else _e -> []
|
_e -> []
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -69,9 +70,9 @@ def friends_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
def show_user(conn, params) do
|
def show_user(conn, params) do
|
||||||
with {:ok, shown} <- TwitterAPI.get_user(params) do
|
with {:ok, shown} <- TwitterAPI.get_user(params) do
|
||||||
if user = conn.assigns.user 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
|
else
|
||||||
render conn, UserView, "show.json", %{user: shown}
|
render(conn, UserView, "show.json", %{user: shown})
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
{:error, msg} ->
|
{:error, msg} ->
|
||||||
|
@ -83,9 +84,11 @@ def user_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
case TwitterAPI.get_user(user, params) do
|
case TwitterAPI.get_user(user, params) do
|
||||||
{:ok, target_user} ->
|
{:ok, target_user} ->
|
||||||
params = Map.merge(params, %{"actor_id" => target_user.ap_id, "whole_db" => true})
|
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
|
conn
|
||||||
|> json_reply(200, statuses |> Jason.encode!)
|
|> json_reply(200, statuses |> Jason.encode!())
|
||||||
|
|
||||||
{:error, msg} ->
|
{:error, msg} ->
|
||||||
bad_request_reply(conn, msg)
|
bad_request_reply(conn, msg)
|
||||||
end
|
end
|
||||||
|
@ -103,29 +106,36 @@ def follow(%{assigns: %{user: user}} = conn, params) do
|
||||||
case TwitterAPI.follow(user, params) do
|
case TwitterAPI.follow(user, params) do
|
||||||
{:ok, user, followed, _activity} ->
|
{:ok, user, followed, _activity} ->
|
||||||
render(conn, UserView, "show.json", %{user: followed, for: user})
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def block(%{assigns: %{user: user}} = conn, params) do
|
def block(%{assigns: %{user: user}} = conn, params) do
|
||||||
case TwitterAPI.block(user, params) do
|
case TwitterAPI.block(user, params) do
|
||||||
{:ok, user, blocked} ->
|
{:ok, user, blocked} ->
|
||||||
render conn, UserView, "show.json", %{user: blocked, for: user}
|
render(conn, UserView, "show.json", %{user: blocked, for: user})
|
||||||
{:error, msg} -> forbidden_json_reply(conn, msg)
|
|
||||||
|
{:error, msg} ->
|
||||||
|
forbidden_json_reply(conn, msg)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def unblock(%{assigns: %{user: user}} = conn, params) do
|
def unblock(%{assigns: %{user: user}} = conn, params) do
|
||||||
case TwitterAPI.unblock(user, params) do
|
case TwitterAPI.unblock(user, params) do
|
||||||
{:ok, user, blocked} ->
|
{:ok, user, blocked} ->
|
||||||
render conn, UserView, "show.json", %{user: blocked, for: user}
|
render(conn, UserView, "show.json", %{user: blocked, for: user})
|
||||||
{:error, msg} -> forbidden_json_reply(conn, msg)
|
|
||||||
|
{:error, msg} ->
|
||||||
|
forbidden_json_reply(conn, msg)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with {:ok, delete} <- CommonAPI.delete(id, user) do
|
with {:ok, delete} <- CommonAPI.delete(id, user) do
|
||||||
json = ActivityRepresenter.to_json(delete, %{user: user, for: user})
|
json = ActivityRepresenter.to_json(delete, %{user: user, for: user})
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> json_reply(200, json)
|
|> json_reply(200, json)
|
||||||
end
|
end
|
||||||
|
@ -135,14 +145,16 @@ def unfollow(%{assigns: %{user: user}} = conn, params) do
|
||||||
case TwitterAPI.unfollow(user, params) do
|
case TwitterAPI.unfollow(user, params) do
|
||||||
{:ok, user, unfollowed} ->
|
{:ok, user, unfollowed} ->
|
||||||
render(conn, UserView, "show.json", %{user: unfollowed, for: user})
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||||
true <- ActivityPub.visible_for_user?(activity, user) do
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -156,6 +168,7 @@ def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
|
||||||
def upload(conn, %{"media" => media}) do
|
def upload(conn, %{"media" => media}) do
|
||||||
response = TwitterAPI.upload(media)
|
response = TwitterAPI.upload(media)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/atom+xml")
|
|> put_resp_content_type("application/atom+xml")
|
||||||
|> send_resp(200, response)
|
|> send_resp(200, response)
|
||||||
|
@ -163,12 +176,14 @@ def upload(conn, %{"media" => media}) do
|
||||||
|
|
||||||
def upload_json(conn, %{"media" => media}) do
|
def upload_json(conn, %{"media" => media}) do
|
||||||
response = TwitterAPI.upload(media, "json")
|
response = TwitterAPI.upload(media, "json")
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> json_reply(200, response)
|
|> json_reply(200, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_by_id_or_ap_id(id) do
|
def get_by_id_or_ap_id(id) do
|
||||||
activity = Repo.get(Activity, id) || Activity.get_create_activity_by_object_ap_id(id)
|
activity = Repo.get(Activity, id) || Activity.get_create_activity_by_object_ap_id(id)
|
||||||
|
|
||||||
if activity.data["type"] == "Create" do
|
if activity.data["type"] == "Create" do
|
||||||
activity
|
activity
|
||||||
else
|
else
|
||||||
|
@ -199,8 +214,8 @@ def register(conn, params) do
|
||||||
render(conn, UserView, "show.json", %{user: user})
|
render(conn, UserView, "show.json", %{user: user})
|
||||||
else
|
else
|
||||||
{:error, errors} ->
|
{:error, errors} ->
|
||||||
conn
|
conn
|
||||||
|> json_reply(400, Jason.encode!(errors))
|
|> json_reply(400, Jason.encode!(errors))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -219,8 +234,9 @@ def update_banner(%{assigns: %{user: user}} = conn, params) do
|
||||||
change <- User.info_changeset(user, %{info: new_info}),
|
change <- User.info_changeset(user, %{info: new_info}),
|
||||||
{:ok, user} <- User.update_and_set_cache(change) do
|
{:ok, user} <- User.update_and_set_cache(change) do
|
||||||
CommonAPI.update(user)
|
CommonAPI.update(user)
|
||||||
%{"url" => [ %{ "href" => href } | _ ]} = object.data
|
%{"url" => [%{"href" => href} | _]} = object.data
|
||||||
response = %{ url: href } |> Jason.encode!
|
response = %{url: href} |> Jason.encode!()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> json_reply(200, response)
|
|> json_reply(200, response)
|
||||||
end
|
end
|
||||||
|
@ -231,8 +247,9 @@ def update_background(%{assigns: %{user: user}} = conn, params) do
|
||||||
new_info <- Map.put(user.info, "background", object.data),
|
new_info <- Map.put(user.info, "background", object.data),
|
||||||
change <- User.info_changeset(user, %{info: new_info}),
|
change <- User.info_changeset(user, %{info: new_info}),
|
||||||
{:ok, _user} <- User.update_and_set_cache(change) do
|
{:ok, _user} <- User.update_and_set_cache(change) do
|
||||||
%{"url" => [ %{ "href" => href } | _ ]} = object.data
|
%{"url" => [%{"href" => href} | _]} = object.data
|
||||||
response = %{ url: href } |> Jason.encode!
|
response = %{url: href} |> Jason.encode!()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> json_reply(200, response)
|
|> json_reply(200, response)
|
||||||
end
|
end
|
||||||
|
@ -285,9 +302,10 @@ def friends(conn, params) do
|
||||||
|
|
||||||
def friends_ids(%{assigns: %{user: user}} = conn, _params) do
|
def friends_ids(%{assigns: %{user: user}} = conn, _params) do
|
||||||
with {:ok, friends} <- User.get_friends(user) do
|
with {:ok, friends} <- User.get_friends(user) do
|
||||||
ids = friends
|
ids =
|
||||||
|> Enum.map(fn x -> x.id end)
|
friends
|
||||||
|> Jason.encode!
|
|> Enum.map(fn x -> x.id end)
|
||||||
|
|> Jason.encode!()
|
||||||
|
|
||||||
json(conn, ids)
|
json(conn, ids)
|
||||||
else
|
else
|
||||||
|
@ -300,11 +318,12 @@ def empty_array(conn, _params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_profile(%{assigns: %{user: user}} = conn, params) do
|
def update_profile(%{assigns: %{user: user}} = conn, params) do
|
||||||
params = if bio = params["description"] do
|
params =
|
||||||
Map.put(params, "bio", bio)
|
if bio = params["description"] do
|
||||||
else
|
Map.put(params, "bio", bio)
|
||||||
params
|
else
|
||||||
end
|
params
|
||||||
|
end
|
||||||
|
|
||||||
with changeset <- User.update_changeset(user, params),
|
with changeset <- User.update_changeset(user, params),
|
||||||
{:ok, user} <- User.update_and_set_cache(changeset) do
|
{:ok, user} <- User.update_and_set_cache(changeset) do
|
||||||
|
@ -339,6 +358,6 @@ defp forbidden_json_reply(conn, error_message) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp error_json(conn, error_message) do
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
|
||||||
|
|
||||||
def render("activity.json", %{activity: %{data: %{"type" => "Announce"}} = activity} = opts) do
|
def render("activity.json", %{activity: %{data: %{"type" => "Announce"}} = activity} = opts) do
|
||||||
user = User.get_by_ap_id(activity.data["actor"])
|
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"])
|
announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
|
||||||
|
|
||||||
text = "#{user.nickname} retweeted a status."
|
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
|
def render("activity.json", %{activity: %{data: %{"type" => "Like"}} = activity} = opts) do
|
||||||
user = User.get_cached_by_ap_id(activity.data["actor"])
|
user = User.get_cached_by_ap_id(activity.data["actor"])
|
||||||
liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
|
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."
|
text = "#{user.nickname} favorited a status."
|
||||||
|
|
||||||
|
@ -57,20 +59,24 @@ def render("activity.json", %{activity: %{data: %{"type" => "Like"}} = activity}
|
||||||
}
|
}
|
||||||
end
|
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"])
|
actor = get_in(activity.data, ["actor"])
|
||||||
user = User.get_cached_by_ap_id(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
|
like_count = object["like_count"] || 0
|
||||||
announcement_count = object["announcement_count"] || 0
|
announcement_count = object["announcement_count"] || 0
|
||||||
favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
|
favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
|
||||||
repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || [])
|
repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || [])
|
||||||
|
|
||||||
attentions = activity.recipients
|
attentions =
|
||||||
|> Enum.map(fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end)
|
activity.recipients
|
||||||
|> Enum.filter(&(&1))
|
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|
||||||
|> Enum.map(fn (user) -> UserView.render("show.json", %{user: user, for: opts[:for]}) end)
|
|> Enum.filter(& &1)
|
||||||
|
|> Enum.map(fn user -> UserView.render("show.json", %{user: user, for: opts[:for]}) end)
|
||||||
|
|
||||||
conversation_id = conversation_id(activity)
|
conversation_id = conversation_id(activity)
|
||||||
|
|
||||||
|
@ -81,14 +87,17 @@ def render("activity.json", %{activity: %{data: %{"type" => "Create", "object" =
|
||||||
|
|
||||||
summary = activity.data["object"]["summary"]
|
summary = activity.data["object"]["summary"]
|
||||||
content = object["content"]
|
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)
|
content =
|
||||||
|> Formatter.emojify(object["emoji"])
|
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,
|
"id" => activity.id,
|
||||||
|
@ -117,7 +126,8 @@ def render("activity.json", %{activity: %{data: %{"type" => "Create", "object" =
|
||||||
defp conversation_id(activity) do
|
defp conversation_id(activity) do
|
||||||
with context when not is_nil(context) <- activity.data["context"] do
|
with context when not is_nil(context) <- activity.data["context"] do
|
||||||
TwitterAPI.context_to_conversation_id(context)
|
TwitterAPI.context_to_conversation_id(context)
|
||||||
else _e -> nil
|
else
|
||||||
|
_e -> nil
|
||||||
end
|
end
|
||||||
end
|
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
|
def render("user.json", %{user: user = %User{}} = assigns) do
|
||||||
image = User.avatar_url(user) |> MediaProxy.url()
|
image = User.avatar_url(user) |> MediaProxy.url()
|
||||||
{following, follows_you, statusnet_blocking} = if assigns[:for] do
|
|
||||||
{
|
{following, follows_you, statusnet_blocking} =
|
||||||
User.following?(assigns[:for], user),
|
if assigns[:for] do
|
||||||
User.following?(user, assigns[:for]),
|
{
|
||||||
User.blocks?(assigns[:for], user)
|
User.following?(assigns[:for], user),
|
||||||
}
|
User.following?(user, assigns[:for]),
|
||||||
else
|
User.blocks?(assigns[:for], user)
|
||||||
{false, false, false}
|
}
|
||||||
end
|
else
|
||||||
|
{false, false, false}
|
||||||
|
end
|
||||||
|
|
||||||
user_info = User.get_cached_user_info(user)
|
user_info = User.get_cached_user_info(user)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"created_at" => user.inserted_at |> Utils.format_naive_asctime,
|
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
|
||||||
"description" => HtmlSanitizeEx.strip_tags(user.bio),
|
"description" => HtmlSanitizeEx.strip_tags(user.bio),
|
||||||
"favourites_count" => 0,
|
"favourites_count" => 0,
|
||||||
"followers_count" => user_info[:follower_count],
|
"followers_count" => user_info[:follower_count],
|
||||||
|
@ -59,9 +61,14 @@ def render("user.json", %{user: user = %User{}} = assigns) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("short.json", %{user: %User{
|
def render("short.json", %{
|
||||||
nickname: nickname, id: id, ap_id: ap_id, name: name
|
user: %User{
|
||||||
}}) do
|
nickname: nickname,
|
||||||
|
id: id,
|
||||||
|
ap_id: ap_id,
|
||||||
|
name: name
|
||||||
|
}
|
||||||
|
}) do
|
||||||
%{
|
%{
|
||||||
"fullname" => name,
|
"fullname" => name,
|
||||||
"id" => id,
|
"id" => id,
|
||||||
|
@ -71,6 +78,6 @@ def render("short.json", %{user: %User{
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp image_url(%{"url" => [ %{ "href" => href } | _ ]}), do: href
|
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
|
||||||
defp image_url(_), do: nil
|
defp image_url(_), do: nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,6 +12,6 @@ def render("500.json", _assigns) do
|
||||||
# In case no render clause matches or no
|
# In case no render clause matches or no
|
||||||
# template is found, let's render it as 500
|
# template is found, let's render it as 500
|
||||||
def template_not_found(_template, assigns) do
|
def template_not_found(_template, assigns) do
|
||||||
render "500.json", assigns
|
render("500.json", assigns)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,8 +26,9 @@ def controller do
|
||||||
|
|
||||||
def view do
|
def view do
|
||||||
quote do
|
quote do
|
||||||
use Phoenix.View, root: "lib/pleroma/web/templates",
|
use Phoenix.View,
|
||||||
namespace: Pleroma.Web
|
root: "lib/pleroma/web/templates",
|
||||||
|
namespace: Pleroma.Web
|
||||||
|
|
||||||
# Import convenience functions from controllers
|
# Import convenience functions from controllers
|
||||||
import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
|
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
|
end
|
||||||
|
|
||||||
def base_url do
|
def base_url do
|
||||||
Pleroma.Web.Endpoint.url
|
Pleroma.Web.Endpoint.url()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,43 +8,56 @@ defmodule Pleroma.Web.WebFinger do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
def host_meta do
|
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
|
end
|
||||||
|
|
||||||
def webfinger(resource, "JSON") do
|
def webfinger(resource, "JSON") do
|
||||||
host = Pleroma.Web.Endpoint.host
|
host = Pleroma.Web.Endpoint.host()
|
||||||
regex = ~r/(acct:)?(?<username>\w+)@#{host}/
|
regex = ~r/(acct:)?(?<username>\w+)@#{host}/
|
||||||
|
|
||||||
with %{"username" => username} <- Regex.named_captures(regex, resource) do
|
with %{"username" => username} <- Regex.named_captures(regex, resource) do
|
||||||
user = User.get_by_nickname(username)
|
user = User.get_by_nickname(username)
|
||||||
{:ok, represent_user(user, "JSON")}
|
{:ok, represent_user(user, "JSON")}
|
||||||
else _e ->
|
else
|
||||||
with user when not is_nil(user) <- User.get_cached_by_ap_id(resource) do
|
_e ->
|
||||||
{:ok, represent_user(user, "JSON")}
|
with user when not is_nil(user) <- User.get_cached_by_ap_id(resource) do
|
||||||
else _e ->
|
{:ok, represent_user(user, "JSON")}
|
||||||
{:error, "Couldn't find user"}
|
else
|
||||||
end
|
_e ->
|
||||||
|
{:error, "Couldn't find user"}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def webfinger(resource, "XML") do
|
def webfinger(resource, "XML") do
|
||||||
host = Pleroma.Web.Endpoint.host
|
host = Pleroma.Web.Endpoint.host()
|
||||||
regex = ~r/(acct:)?(?<username>\w+)@#{host}/
|
regex = ~r/(acct:)?(?<username>\w+)@#{host}/
|
||||||
|
|
||||||
with %{"username" => username} <- Regex.named_captures(regex, resource) do
|
with %{"username" => username} <- Regex.named_captures(regex, resource) do
|
||||||
user = User.get_by_nickname(username)
|
user = User.get_by_nickname(username)
|
||||||
{:ok, represent_user(user, "XML")}
|
{:ok, represent_user(user, "XML")}
|
||||||
else _e ->
|
else
|
||||||
with user when not is_nil(user) <- User.get_cached_by_ap_id(resource) do
|
_e ->
|
||||||
{:ok, represent_user(user, "XML")}
|
with user when not is_nil(user) <- User.get_cached_by_ap_id(resource) do
|
||||||
else _e ->
|
{:ok, represent_user(user, "XML")}
|
||||||
{:error, "Couldn't find user"}
|
else
|
||||||
end
|
_e ->
|
||||||
|
{:error, "Couldn't find user"}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -52,16 +65,28 @@ def represent_user(user, "JSON") do
|
||||||
{:ok, user} = ensure_keys_present(user)
|
{:ok, user} = ensure_keys_present(user)
|
||||||
{:ok, _private, public} = Salmon.keys_from_pem(user.info["keys"])
|
{:ok, _private, public} = Salmon.keys_from_pem(user.info["keys"])
|
||||||
magic_key = Salmon.encode_key(public)
|
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],
|
"aliases" => [user.ap_id],
|
||||||
"links" => [
|
"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" => "salmon", "href" => OStatus.salmon_path(user)},
|
||||||
%{"rel" => "magic-public-key", "href" => "data:application/magic-public-key,#{magic_key}"},
|
%{"rel" => "magic-public-key", "href" => "data:application/magic-public-key,#{magic_key}"},
|
||||||
%{"rel" => "self", "type" => "application/activity+json", "href" => user.ap_id},
|
%{"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
|
end
|
||||||
|
@ -70,30 +95,42 @@ def represent_user(user, "XML") do
|
||||||
{:ok, user} = ensure_keys_present(user)
|
{:ok, user} = ensure_keys_present(user)
|
||||||
{:ok, _private, public} = Salmon.keys_from_pem(user.info["keys"])
|
{:ok, _private, public} = Salmon.keys_from_pem(user.info["keys"])
|
||||||
magic_key = Salmon.encode_key(public)
|
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},
|
{:Alias, user.ap_id},
|
||||||
{:Link, %{rel: "http://schemas.google.com/g/2010#updates-from", type: "application/atom+xml", href: OStatus.feed_path(user)}},
|
{:Link,
|
||||||
{:Link, %{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)
|
||||||
|
}},
|
||||||
|
{: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: "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: "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
|
end
|
||||||
|
|
||||||
# This seems a better fit in Salmon
|
# This seems a better fit in Salmon
|
||||||
def ensure_keys_present(user) do
|
def ensure_keys_present(user) do
|
||||||
info = user.info || %{}
|
info = user.info || %{}
|
||||||
|
|
||||||
if info["keys"] do
|
if info["keys"] do
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
{:ok, pem} = Salmon.generate_rsa_pem
|
{:ok, pem} = Salmon.generate_rsa_pem()
|
||||||
info = Map.put(info, "keys", pem)
|
info = Map.put(info, "keys", pem)
|
||||||
|
|
||||||
Ecto.Changeset.change(user, info: info)
|
Ecto.Changeset.change(user, info: info)
|
||||||
|> User.update_and_set_cache()
|
|> User.update_and_set_cache()
|
||||||
end
|
end
|
||||||
|
@ -102,11 +139,28 @@ def ensure_keys_present(user) do
|
||||||
defp webfinger_from_xml(doc) do
|
defp webfinger_from_xml(doc) do
|
||||||
magic_key = XML.string_from_xpath(~s{//Link[@rel="magic-public-key"]/@href}, doc)
|
magic_key = XML.string_from_xpath(~s{//Link[@rel="magic-public-key"]/@href}, doc)
|
||||||
"data:application/magic-public-key," <> magic_key = magic_key
|
"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)
|
subject = XML.string_from_xpath("//Subject", doc)
|
||||||
salmon = XML.string_from_xpath(~s{//Link[@rel="salmon"]/@href}, 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 = %{
|
data = %{
|
||||||
"magic_key" => magic_key,
|
"magic_key" => magic_key,
|
||||||
"topic" => topic,
|
"topic" => topic,
|
||||||
|
@ -115,41 +169,51 @@ defp webfinger_from_xml(doc) do
|
||||||
"subscribe_address" => subscribe_address,
|
"subscribe_address" => subscribe_address,
|
||||||
"ap_id" => ap_id
|
"ap_id" => ap_id
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp webfinger_from_json(doc) do
|
defp webfinger_from_json(doc) do
|
||||||
data = Enum.reduce(doc["links"], %{"subject" => doc["subject"]}, fn (link, data) ->
|
data =
|
||||||
case {link["type"], link["rel"]} do
|
Enum.reduce(doc["links"], %{"subject" => doc["subject"]}, fn link, data ->
|
||||||
{"application/activity+json", "self"} ->
|
case {link["type"], link["rel"]} do
|
||||||
Map.put(data, "ap_id", link["href"])
|
{"application/activity+json", "self"} ->
|
||||||
{_, "magic-public-key"} ->
|
Map.put(data, "ap_id", link["href"])
|
||||||
"data:application/magic-public-key," <> magic_key = link["href"]
|
|
||||||
Map.put(data, "magic_key", magic_key)
|
{_, "magic-public-key"} ->
|
||||||
{"application/atom+xml", "http://schemas.google.com/g/2010#updates-from"} ->
|
"data:application/magic-public-key," <> magic_key = link["href"]
|
||||||
Map.put(data, "topic", link["href"])
|
Map.put(data, "magic_key", magic_key)
|
||||||
{_, "salmon"} ->
|
|
||||||
Map.put(data, "salmon", link["href"])
|
{"application/atom+xml", "http://schemas.google.com/g/2010#updates-from"} ->
|
||||||
{_, "http://ostatus.org/schema/1.0/subscribe"} ->
|
Map.put(data, "topic", link["href"])
|
||||||
Map.put(data, "subscribe_address", link["template"])
|
|
||||||
_ ->
|
{_, "salmon"} ->
|
||||||
Logger.debug("Unhandled type: #{inspect(link["type"])}")
|
Map.put(data, "salmon", link["href"])
|
||||||
data
|
|
||||||
end
|
{_, "http://ostatus.org/schema/1.0/subscribe"} ->
|
||||||
end)
|
Map.put(data, "subscribe_address", link["template"])
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Logger.debug("Unhandled type: #{inspect(link["type"])}")
|
||||||
|
data
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_template_from_xml(body) do
|
def get_template_from_xml(body) do
|
||||||
xpath = "//Link[@rel='lrdd' and @type='application/xrd+xml']/@template"
|
xpath = "//Link[@rel='lrdd' and @type='application/xrd+xml']/@template"
|
||||||
|
|
||||||
with doc when doc != :error <- XML.parse_document(body),
|
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}
|
{:ok, template}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_lrdd_template(domain) do
|
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)
|
get_template_from_xml(body)
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -163,28 +227,38 @@ def find_lrdd_template(domain) do
|
||||||
|
|
||||||
def finger(account) do
|
def finger(account) do
|
||||||
account = String.trim_leading(account, "@")
|
account = String.trim_leading(account, "@")
|
||||||
domain = with [_name, domain] <- String.split(account, "@") do
|
|
||||||
domain
|
domain =
|
||||||
else _e ->
|
with [_name, domain] <- String.split(account, "@") do
|
||||||
URI.parse(account).host
|
domain
|
||||||
end
|
else
|
||||||
|
_e ->
|
||||||
|
URI.parse(account).host
|
||||||
|
end
|
||||||
|
|
||||||
case find_lrdd_template(domain) do
|
case find_lrdd_template(domain) do
|
||||||
{:ok, template} ->
|
{:ok, template} ->
|
||||||
address = String.replace(template, "{uri}", URI.encode(account))
|
address = String.replace(template, "{uri}", URI.encode(account))
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
address = "http://#{domain}/.well-known/webfinger?resource=acct:#{account}"
|
address = "http://#{domain}/.well-known/webfinger?resource=acct:#{account}"
|
||||||
end
|
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
|
{:ok, %{status_code: status_code, body: body}} when status_code in 200..299 <- response do
|
||||||
doc = XML.parse_document(body)
|
doc = XML.parse_document(body)
|
||||||
if doc != :error do
|
|
||||||
webfinger_from_xml(doc)
|
if doc != :error do
|
||||||
else
|
webfinger_from_xml(doc)
|
||||||
{:ok, doc} = Jason.decode(body)
|
else
|
||||||
webfinger_from_json(doc)
|
{:ok, doc} = Jason.decode(body)
|
||||||
end
|
webfinger_from_json(doc)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
e ->
|
e ->
|
||||||
Logger.debug(fn -> "Couldn't finger #{account}" end)
|
Logger.debug(fn -> "Couldn't finger #{account}" end)
|
||||||
|
|
|
@ -4,7 +4,7 @@ defmodule Pleroma.Web.WebFinger.WebFingerController do
|
||||||
alias Pleroma.Web.WebFinger
|
alias Pleroma.Web.WebFinger
|
||||||
|
|
||||||
def host_meta(conn, _params) do
|
def host_meta(conn, _params) do
|
||||||
xml = WebFinger.host_meta
|
xml = WebFinger.host_meta()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/xrd+xml")
|
|> put_resp_content_type("application/xrd+xml")
|
||||||
|
@ -21,12 +21,14 @@ def webfinger(conn, %{"resource" => resource}) do
|
||||||
else
|
else
|
||||||
_e -> send_resp(conn, 404, "Couldn't find user")
|
_e -> send_resp(conn, 404, "Couldn't find user")
|
||||||
end
|
end
|
||||||
|
|
||||||
n when n in ["json", "jrd+json"] ->
|
n when n in ["json", "jrd+json"] ->
|
||||||
with {:ok, response} <- WebFinger.webfinger(resource, "JSON") do
|
with {:ok, response} <- WebFinger.webfinger(resource, "JSON") do
|
||||||
json(conn, response)
|
json(conn, response)
|
||||||
else
|
else
|
||||||
_e -> send_resp(conn, 404, "Couldn't find user")
|
_e -> send_resp(conn, 404, "Couldn't find user")
|
||||||
end
|
end
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
send_resp(conn, 404, "Unsupported format")
|
send_resp(conn, 404, "Unsupported format")
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,15 +26,16 @@ def verify(subscription, getter \\ &@httpoison.get/3) do
|
||||||
url = hd(String.split(subscription.callback, "?"))
|
url = hd(String.split(subscription.callback, "?"))
|
||||||
query = URI.parse(subscription.callback).query || ""
|
query = URI.parse(subscription.callback).query || ""
|
||||||
params = Map.merge(params, URI.decode_query(query))
|
params = Map.merge(params, URI.decode_query(query))
|
||||||
with {:ok, response} <- getter.(url, [], [params: params]),
|
|
||||||
^challenge <- response.body
|
with {:ok, response} <- getter.(url, [], params: params),
|
||||||
do
|
^challenge <- response.body do
|
||||||
changeset = Changeset.change(subscription, %{state: "active"})
|
changeset = Changeset.change(subscription, %{state: "active"})
|
||||||
Repo.update(changeset)
|
Repo.update(changeset)
|
||||||
else e ->
|
else
|
||||||
Logger.debug("Couldn't verify subscription")
|
e ->
|
||||||
Logger.debug(inspect(e))
|
Logger.debug("Couldn't verify subscription")
|
||||||
{:error, subscription}
|
Logger.debug(inspect(e))
|
||||||
|
{:error, subscription}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -46,17 +47,24 @@ def verify(subscription, getter \\ &@httpoison.get/3) do
|
||||||
"Undo",
|
"Undo",
|
||||||
"Delete"
|
"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.
|
# TODO: Only send to still valid subscriptions.
|
||||||
query = from sub in WebsubServerSubscription,
|
query =
|
||||||
where: sub.topic == ^topic and sub.state == "active",
|
from(
|
||||||
where: fragment("? > NOW()", sub.valid_until)
|
sub in WebsubServerSubscription,
|
||||||
|
where: sub.topic == ^topic and sub.state == "active",
|
||||||
|
where: fragment("? > NOW()", sub.valid_until)
|
||||||
|
)
|
||||||
|
|
||||||
subscriptions = Repo.all(query)
|
subscriptions = Repo.all(query)
|
||||||
Enum.each(subscriptions, fn(sub) ->
|
|
||||||
response = user
|
Enum.each(subscriptions, fn sub ->
|
||||||
|> FeedRepresenter.to_simple_form([activity], [user])
|
response =
|
||||||
|> :xmerl.export_simple(:xmerl_xml)
|
user
|
||||||
|> to_string
|
|> FeedRepresenter.to_simple_form([activity], [user])
|
||||||
|
|> :xmerl.export_simple(:xmerl_xml)
|
||||||
|
|> to_string
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
xml: response,
|
xml: response,
|
||||||
|
@ -64,22 +72,24 @@ def publish(topic, user, %{data: %{"type" => type}} = activity) when type in @su
|
||||||
callback: sub.callback,
|
callback: sub.callback,
|
||||||
secret: sub.secret
|
secret: sub.secret
|
||||||
}
|
}
|
||||||
|
|
||||||
Pleroma.Web.Federator.enqueue(:publish_single_websub, data)
|
Pleroma.Web.Federator.enqueue(:publish_single_websub, data)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
def publish(_,_,_), do: ""
|
|
||||||
|
def publish(_, _, _), do: ""
|
||||||
|
|
||||||
def sign(secret, doc) 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
|
end
|
||||||
|
|
||||||
def incoming_subscription_request(user, %{"hub.mode" => "subscribe"} = params) do
|
def incoming_subscription_request(user, %{"hub.mode" => "subscribe"} = params) do
|
||||||
with {:ok, topic} <- valid_topic(params, user),
|
with {:ok, topic} <- valid_topic(params, user),
|
||||||
{:ok, lease_time} <- lease_time(params),
|
{:ok, lease_time} <- lease_time(params),
|
||||||
secret <- params["hub.secret"],
|
secret <- params["hub.secret"],
|
||||||
callback <- params["hub.callback"]
|
callback <- params["hub.callback"] do
|
||||||
do
|
|
||||||
subscription = get_subscription(topic, callback)
|
subscription = get_subscription(topic, callback)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
state: subscription.state || "requested",
|
state: subscription.state || "requested",
|
||||||
topic: topic,
|
topic: topic,
|
||||||
|
@ -90,18 +100,20 @@ def incoming_subscription_request(user, %{"hub.mode" => "subscribe"} = params) d
|
||||||
change = Changeset.change(subscription, data)
|
change = Changeset.change(subscription, data)
|
||||||
websub = Repo.insert_or_update!(change)
|
websub = Repo.insert_or_update!(change)
|
||||||
|
|
||||||
change = Changeset.change(websub, %{valid_until:
|
change =
|
||||||
NaiveDateTime.add(websub.updated_at, lease_time)})
|
Changeset.change(websub, %{valid_until: NaiveDateTime.add(websub.updated_at, lease_time)})
|
||||||
|
|
||||||
websub = Repo.update!(change)
|
websub = Repo.update!(change)
|
||||||
|
|
||||||
Pleroma.Web.Federator.enqueue(:verify_websub, websub)
|
Pleroma.Web.Federator.enqueue(:verify_websub, websub)
|
||||||
|
|
||||||
{:ok, websub}
|
{:ok, websub}
|
||||||
else {:error, reason} ->
|
else
|
||||||
Logger.debug("Couldn't create subscription")
|
{:error, reason} ->
|
||||||
Logger.debug(inspect(reason))
|
Logger.debug("Couldn't create subscription")
|
||||||
|
Logger.debug(inspect(reason))
|
||||||
|
|
||||||
{:error, reason}
|
{:error, reason}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -112,7 +124,8 @@ defp get_subscription(topic, callback) do
|
||||||
|
|
||||||
# Temp hack for mastodon.
|
# Temp hack for mastodon.
|
||||||
defp lease_time(%{"hub.lease_seconds" => ""}) do
|
defp lease_time(%{"hub.lease_seconds" => ""}) do
|
||||||
{:ok, 60 * 60 * 24 * 3} # three days
|
# three days
|
||||||
|
{:ok, 60 * 60 * 24 * 3}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp lease_time(%{"hub.lease_seconds" => lease_seconds}) do
|
defp lease_time(%{"hub.lease_seconds" => lease_seconds}) do
|
||||||
|
@ -120,7 +133,8 @@ defp lease_time(%{"hub.lease_seconds" => lease_seconds}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp lease_time(_) do
|
defp lease_time(_) do
|
||||||
{:ok, 60 * 60 * 24 * 3} # three days
|
# three days
|
||||||
|
{:ok, 60 * 60 * 24 * 3}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp valid_topic(%{"hub.topic" => topic}, user) do
|
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
|
def subscribe(subscriber, subscribed, requester \\ &request_subscription/1) do
|
||||||
topic = subscribed.info["topic"]
|
topic = subscribed.info["topic"]
|
||||||
# FIXME: Race condition, use transactions
|
# FIXME: Race condition, use transactions
|
||||||
{:ok, subscription} = with subscription when not is_nil(subscription) <- Repo.get_by(WebsubClientSubscription, topic: topic) do
|
{:ok, subscription} =
|
||||||
subscribers = [subscriber.ap_id | subscription.subscribers] |> Enum.uniq
|
with subscription when not is_nil(subscription) <-
|
||||||
change = Ecto.Changeset.change(subscription, %{subscribers: subscribers})
|
Repo.get_by(WebsubClientSubscription, topic: topic) do
|
||||||
Repo.update(change)
|
subscribers = [subscriber.ap_id | subscription.subscribers] |> Enum.uniq()
|
||||||
else _e ->
|
change = Ecto.Changeset.change(subscription, %{subscribers: subscribers})
|
||||||
subscription = %WebsubClientSubscription{
|
Repo.update(change)
|
||||||
topic: topic,
|
else
|
||||||
hub: subscribed.info["hub"],
|
_e ->
|
||||||
subscribers: [subscriber.ap_id],
|
subscription = %WebsubClientSubscription{
|
||||||
state: "requested",
|
topic: topic,
|
||||||
secret: :crypto.strong_rand_bytes(8) |> Base.url_encode64,
|
hub: subscribed.info["hub"],
|
||||||
user: subscribed
|
subscribers: [subscriber.ap_id],
|
||||||
}
|
state: "requested",
|
||||||
Repo.insert(subscription)
|
secret: :crypto.strong_rand_bytes(8) |> Base.url_encode64(),
|
||||||
end
|
user: subscribed
|
||||||
|
}
|
||||||
|
|
||||||
|
Repo.insert(subscription)
|
||||||
|
end
|
||||||
|
|
||||||
requester.(subscription)
|
requester.(subscription)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -159,24 +178,25 @@ def gather_feed_data(topic, getter \\ &@httpoison.get/1) do
|
||||||
doc <- XML.parse_document(body),
|
doc <- XML.parse_document(body),
|
||||||
uri when not is_nil(uri) <- XML.string_from_xpath("/feed/author[1]/uri", doc),
|
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
|
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)
|
name = XML.string_from_xpath("/feed/author[1]/name", doc)
|
||||||
preferredUsername = XML.string_from_xpath("/feed/author[1]/poco:preferredUsername", doc)
|
preferredUsername = XML.string_from_xpath("/feed/author[1]/poco:preferredUsername", doc)
|
||||||
displayName = XML.string_from_xpath("/feed/author[1]/poco:displayName", doc)
|
displayName = XML.string_from_xpath("/feed/author[1]/poco:displayName", doc)
|
||||||
avatar = OStatus.make_avatar_object(doc)
|
avatar = OStatus.make_avatar_object(doc)
|
||||||
bio = XML.string_from_xpath("/feed/author[1]/summary", doc)
|
bio = XML.string_from_xpath("/feed/author[1]/summary", doc)
|
||||||
|
|
||||||
{:ok, %{
|
{:ok,
|
||||||
"uri" => uri,
|
%{
|
||||||
"hub" => hub,
|
"uri" => uri,
|
||||||
"nickname" => preferredUsername || name,
|
"hub" => hub,
|
||||||
"name" => displayName || name,
|
"nickname" => preferredUsername || name,
|
||||||
"host" => URI.parse(uri).host,
|
"name" => displayName || name,
|
||||||
"avatar" => avatar,
|
"host" => URI.parse(uri).host,
|
||||||
"bio" => bio
|
"avatar" => avatar,
|
||||||
}}
|
"bio" => bio
|
||||||
else e ->
|
}}
|
||||||
{:error, e}
|
else
|
||||||
|
e ->
|
||||||
|
{:error, e}
|
||||||
end
|
end
|
||||||
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
|
# This checks once a second if we are confirmed yet
|
||||||
websub_checker = fn ->
|
websub_checker = fn ->
|
||||||
helper = fn (helper) ->
|
helper = fn helper ->
|
||||||
:timer.sleep(1000)
|
:timer.sleep(1000)
|
||||||
websub = Repo.get_by(WebsubClientSubscription, id: websub.id, state: "accepted")
|
websub = Repo.get_by(WebsubClientSubscription, id: websub.id, state: "accepted")
|
||||||
if websub, do: websub, else: helper.(helper)
|
if websub, do: websub, else: helper.(helper)
|
||||||
end
|
end
|
||||||
|
|
||||||
helper.(helper)
|
helper.(helper)
|
||||||
end
|
end
|
||||||
|
|
||||||
task = Task.async(websub_checker)
|
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} <- Task.yield(task, timeout) do
|
||||||
{:ok, websub}
|
{:ok, websub}
|
||||||
else e ->
|
else
|
||||||
Task.shutdown(task)
|
e ->
|
||||||
|
Task.shutdown(task)
|
||||||
|
|
||||||
change = Ecto.Changeset.change(websub, %{state: "rejected"})
|
change = Ecto.Changeset.change(websub, %{state: "rejected"})
|
||||||
{:ok, websub} = Repo.update(change)
|
{:ok, websub} = Repo.update(change)
|
||||||
|
|
||||||
Logger.debug(fn -> "Couldn't confirm subscription: #{inspect(websub)}" end)
|
Logger.debug(fn -> "Couldn't confirm subscription: #{inspect(websub)}" end)
|
||||||
Logger.debug(fn -> "error: #{inspect(e)}" end)
|
Logger.debug(fn -> "error: #{inspect(e)}" end)
|
||||||
|
|
||||||
{:error, websub}
|
{:error, websub}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def refresh_subscriptions(delta \\ 60 * 60 * 24) do
|
def refresh_subscriptions(delta \\ 60 * 60 * 24) do
|
||||||
Logger.debug("Refreshing subscriptions")
|
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,
|
query = from(sub in WebsubClientSubscription, where: sub.valid_until < ^cut_off)
|
||||||
where: sub.valid_until < ^cut_off
|
|
||||||
|
|
||||||
subs = Repo.all(query)
|
subs = Repo.all(query)
|
||||||
|
|
||||||
Enum.each(subs, fn (sub) ->
|
Enum.each(subs, fn sub ->
|
||||||
Pleroma.Web.Federator.enqueue(:request_subscription, sub)
|
Pleroma.Web.Federator.enqueue(:request_subscription, sub)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,13 +3,13 @@ defmodule Pleroma.Web.Websub.WebsubClientSubscription do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
schema "websub_client_subscriptions" do
|
schema "websub_client_subscriptions" do
|
||||||
field :topic, :string
|
field(:topic, :string)
|
||||||
field :secret, :string
|
field(:secret, :string)
|
||||||
field :valid_until, :naive_datetime
|
field(:valid_until, :naive_datetime)
|
||||||
field :state, :string
|
field(:state, :string)
|
||||||
field :subscribers, {:array, :string}, default: []
|
field(:subscribers, {:array, :string}, default: [])
|
||||||
field :hub, :string
|
field(:hub, :string)
|
||||||
belongs_to :user, User
|
belongs_to(:user, User)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,36 +8,49 @@ defmodule Pleroma.Web.Websub.WebsubController do
|
||||||
def websub_subscription_request(conn, %{"nickname" => nickname} = params) do
|
def websub_subscription_request(conn, %{"nickname" => nickname} = params) do
|
||||||
user = User.get_cached_by_nickname(nickname)
|
user = User.get_cached_by_nickname(nickname)
|
||||||
|
|
||||||
with {:ok, _websub} <- Websub.incoming_subscription_request(user, params)
|
with {:ok, _websub} <- Websub.incoming_subscription_request(user, params) do
|
||||||
do
|
|
||||||
conn
|
conn
|
||||||
|> send_resp(202, "Accepted")
|
|> send_resp(202, "Accepted")
|
||||||
else {:error, reason} ->
|
else
|
||||||
conn
|
{:error, reason} ->
|
||||||
|> send_resp(500, reason)
|
conn
|
||||||
|
|> send_resp(500, reason)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Extract this into the Websub module
|
# 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("Got WebSub confirmation")
|
||||||
Logger.debug(inspect(params))
|
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
|
lease_seconds =
|
||||||
valid_until = NaiveDateTime.add(NaiveDateTime.utc_now, 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})
|
change = Ecto.Changeset.change(websub, %{state: "accepted", valid_until: valid_until})
|
||||||
{:ok, _websub} = Repo.update(change)
|
{:ok, _websub} = Repo.update(change)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> send_resp(200, challenge)
|
|> send_resp(200, challenge)
|
||||||
else _e ->
|
else
|
||||||
conn
|
_e ->
|
||||||
|> send_resp(500, "Error")
|
conn
|
||||||
|
|> send_resp(500, "Error")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -48,12 +61,15 @@ def websub_incoming(conn, %{"id" => id}) do
|
||||||
{:ok, body, _conn} = read_body(conn),
|
{:ok, body, _conn} = read_body(conn),
|
||||||
^signature <- Websub.sign(websub.secret, body) do
|
^signature <- Websub.sign(websub.secret, body) do
|
||||||
Federator.enqueue(:incoming_doc, body)
|
Federator.enqueue(:incoming_doc, body)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> send_resp(200, "OK")
|
|> send_resp(200, "OK")
|
||||||
else _e ->
|
else
|
||||||
Logger.debug("Can't handle incoming subscription post")
|
_e ->
|
||||||
conn
|
Logger.debug("Can't handle incoming subscription post")
|
||||||
|> send_resp(500, "Error")
|
|
||||||
|
conn
|
||||||
|
|> send_resp(500, "Error")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,11 +2,11 @@ defmodule Pleroma.Web.Websub.WebsubServerSubscription do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
schema "websub_server_subscriptions" do
|
schema "websub_server_subscriptions" do
|
||||||
field :topic, :string
|
field(:topic, :string)
|
||||||
field :callback, :string
|
field(:callback, :string)
|
||||||
field :secret, :string
|
field(:secret, :string)
|
||||||
field :valid_until, :naive_datetime
|
field(:valid_until, :naive_datetime)
|
||||||
field :state, :string
|
field(:state, :string)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,21 +2,24 @@ defmodule Pleroma.Web.XML do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
def string_from_xpath(_, :error), do: nil
|
def string_from_xpath(_, :error), do: nil
|
||||||
|
|
||||||
def string_from_xpath(xpath, doc) do
|
def string_from_xpath(xpath, doc) do
|
||||||
{:xmlObj, :string, res} = :xmerl_xpath.string('string(#{xpath})', doc)
|
{:xmlObj, :string, res} = :xmerl_xpath.string('string(#{xpath})', doc)
|
||||||
|
|
||||||
res = res
|
res =
|
||||||
|> to_string
|
res
|
||||||
|> String.trim
|
|> to_string
|
||||||
|
|> String.trim()
|
||||||
|
|
||||||
if res == "", do: nil, else: res
|
if res == "", do: nil, else: res
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_document(text) do
|
def parse_document(text) do
|
||||||
try do
|
try do
|
||||||
{doc, _rest} = text
|
{doc, _rest} =
|
||||||
|> :binary.bin_to_list
|
text
|
||||||
|> :xmerl_scan.string
|
|> :binary.bin_to_list()
|
||||||
|
|> :xmerl_scan.string()
|
||||||
|
|
||||||
doc
|
doc
|
||||||
catch
|
catch
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
defmodule Phoenix.Transports.WebSocket.Raw do
|
defmodule Phoenix.Transports.WebSocket.Raw do
|
||||||
import Plug.Conn, only: [
|
import Plug.Conn,
|
||||||
fetch_query_params: 1,
|
only: [
|
||||||
send_resp: 3
|
fetch_query_params: 1,
|
||||||
]
|
send_resp: 3
|
||||||
|
]
|
||||||
|
|
||||||
alias Phoenix.Socket.Transport
|
alias Phoenix.Socket.Transport
|
||||||
|
|
||||||
def default_config do
|
def default_config do
|
||||||
|
@ -16,21 +18,24 @@ def default_config do
|
||||||
def init(%Plug.Conn{method: "GET"} = conn, {endpoint, handler, transport}) do
|
def init(%Plug.Conn{method: "GET"} = conn, {endpoint, handler, transport}) do
|
||||||
{_, opts} = handler.__transport__(transport)
|
{_, opts} = handler.__transport__(transport)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> fetch_query_params
|
conn
|
||||||
|> Transport.transport_log(opts[:transport_log])
|
|> fetch_query_params
|
||||||
|> Transport.force_ssl(handler, endpoint, opts)
|
|> Transport.transport_log(opts[:transport_log])
|
||||||
|> Transport.check_origin(handler, endpoint, opts)
|
|> Transport.force_ssl(handler, endpoint, opts)
|
||||||
|
|> Transport.check_origin(handler, endpoint, opts)
|
||||||
|
|
||||||
case conn do
|
case conn do
|
||||||
%{halted: false} = conn ->
|
%{halted: false} = conn ->
|
||||||
case Transport.connect(endpoint, handler, transport, __MODULE__, nil, conn.params) do
|
case Transport.connect(endpoint, handler, transport, __MODULE__, nil, conn.params) do
|
||||||
{:ok, socket} ->
|
{:ok, socket} ->
|
||||||
{:ok, conn, {__MODULE__, {socket, opts}}}
|
{:ok, conn, {__MODULE__, {socket, opts}}}
|
||||||
|
|
||||||
:error ->
|
:error ->
|
||||||
send_resp(conn, :forbidden, "")
|
send_resp(conn, :forbidden, "")
|
||||||
{:error, conn}
|
{:error, conn}
|
||||||
end
|
end
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
{:error, conn}
|
{:error, conn}
|
||||||
end
|
end
|
||||||
|
@ -52,16 +57,19 @@ def ws_handle(op, data, state) do
|
||||||
|> case do
|
|> case do
|
||||||
{op, data} ->
|
{op, data} ->
|
||||||
{:reply, {op, data}, state}
|
{:reply, {op, data}, state}
|
||||||
|
|
||||||
{op, data, state} ->
|
{op, data, state} ->
|
||||||
{:reply, {op, data}, state}
|
{:reply, {op, data}, state}
|
||||||
|
|
||||||
%{} = state ->
|
%{} = state ->
|
||||||
{:ok, state}
|
{:ok, state}
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
{:ok, state}
|
{:ok, state}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def ws_info({_,_} = tuple, state) do
|
def ws_info({_, _} = tuple, state) do
|
||||||
{:reply, tuple, state}
|
{:reply, tuple, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ def to_xml(content) when is_list(content) do
|
||||||
for element <- content do
|
for element <- content do
|
||||||
to_xml(element)
|
to_xml(element)
|
||||||
end
|
end
|
||||||
|> Enum.join
|
|> Enum.join()
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_xml(%NaiveDateTime{} = time) do
|
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)
|
def to_doc(content), do: ~s(<?xml version="1.0" encoding="UTF-8"?>) <> to_xml(content)
|
||||||
|
|
||||||
defp make_open_tag(tag, attributes) do
|
defp make_open_tag(tag, attributes) do
|
||||||
attributes_string = for {attribute, value} <- attributes do
|
attributes_string =
|
||||||
"#{attribute}=\"#{value}\""
|
for {attribute, value} <- attributes do
|
||||||
end |> Enum.join(" ")
|
"#{attribute}=\"#{value}\""
|
||||||
|
end
|
||||||
|
|> Enum.join(" ")
|
||||||
|
|
||||||
[tag, attributes_string] |> Enum.join(" ") |> String.trim
|
[tag, attributes_string] |> Enum.join(" ") |> String.trim()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
65
mix.exs
65
mix.exs
|
@ -2,48 +2,51 @@ defmodule Pleroma.Mixfile do
|
||||||
use Mix.Project
|
use Mix.Project
|
||||||
|
|
||||||
def project do
|
def project do
|
||||||
[app: :pleroma,
|
[
|
||||||
version: "0.9.0",
|
app: :pleroma,
|
||||||
elixir: "~> 1.4",
|
version: "0.9.0",
|
||||||
elixirc_paths: elixirc_paths(Mix.env),
|
elixir: "~> 1.4",
|
||||||
compilers: [:phoenix, :gettext] ++ Mix.compilers,
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
start_permanent: Mix.env == :prod,
|
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
|
||||||
aliases: aliases(),
|
start_permanent: Mix.env() == :prod,
|
||||||
deps: deps()]
|
aliases: aliases(),
|
||||||
|
deps: deps()
|
||||||
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Configuration for the OTP application.
|
# Configuration for the OTP application.
|
||||||
#
|
#
|
||||||
# Type `mix help compile.app` for more information.
|
# Type `mix help compile.app` for more information.
|
||||||
def application do
|
def application do
|
||||||
[mod: {Pleroma.Application, []},
|
[mod: {Pleroma.Application, []}, extra_applications: [:logger, :runtime_tools, :comeonin]]
|
||||||
extra_applications: [:logger, :runtime_tools, :comeonin]]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Specifies which paths to compile per environment.
|
# Specifies which paths to compile per environment.
|
||||||
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
||||||
defp elixirc_paths(_), do: ["lib"]
|
defp elixirc_paths(_), do: ["lib"]
|
||||||
|
|
||||||
# Specifies your project dependencies.
|
# Specifies your project dependencies.
|
||||||
#
|
#
|
||||||
# Type `mix help deps` for examples and options.
|
# Type `mix help deps` for examples and options.
|
||||||
defp deps do
|
defp deps do
|
||||||
[{:phoenix, "~> 1.3.0"},
|
[
|
||||||
{:phoenix_pubsub, "~> 1.0"},
|
{:phoenix, "~> 1.3.0"},
|
||||||
{:phoenix_ecto, "~> 3.2"},
|
{:phoenix_pubsub, "~> 1.0"},
|
||||||
{:postgrex, ">= 0.0.0"},
|
{:phoenix_ecto, "~> 3.2"},
|
||||||
{:gettext, "~> 0.11"},
|
{:postgrex, ">= 0.0.0"},
|
||||||
{:cowboy, "~> 1.0", override: true},
|
{:gettext, "~> 0.11"},
|
||||||
{:comeonin, "~> 3.0"},
|
{:cowboy, "~> 1.0", override: true},
|
||||||
{:trailing_format_plug, "~> 0.0.5" },
|
{:comeonin, "~> 3.0"},
|
||||||
{:html_sanitize_ex, "~> 1.3.0-rc1"},
|
{:trailing_format_plug, "~> 0.0.5"},
|
||||||
{:phoenix_html, "~> 2.10"},
|
{:html_sanitize_ex, "~> 1.3.0-rc1"},
|
||||||
{:calendar, "~> 0.16.1"},
|
{:phoenix_html, "~> 2.10"},
|
||||||
{:cachex, "~> 2.1"},
|
{:calendar, "~> 0.16.1"},
|
||||||
{:httpoison, "~> 0.11.2"},
|
{:cachex, "~> 2.1"},
|
||||||
{:jason, "~> 1.0"},
|
{:httpoison, "~> 0.11.2"},
|
||||||
{:ex_machina, "~> 2.0", only: :test},
|
{:jason, "~> 1.0"},
|
||||||
{:credo, "~> 0.7", only: [:dev, :test]}]
|
{:ex_machina, "~> 2.0", only: :test},
|
||||||
|
{:credo, "~> 0.7", only: [:dev, :test]}
|
||||||
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Aliases are shortcuts or tasks specific to the current project.
|
# 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.
|
# See the documentation for `Mix` for more info on aliases.
|
||||||
defp aliases do
|
defp aliases do
|
||||||
["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
|
[
|
||||||
"ecto.reset": ["ecto.drop", "ecto.setup"],
|
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
|
||||||
"test": ["ecto.create --quiet", "ecto.migrate", "test"]]
|
"ecto.reset": ["ecto.drop", "ecto.setup"],
|
||||||
|
test: ["ecto.create --quiet", "ecto.migrate", "test"]
|
||||||
|
]
|
||||||
end
|
end
|
||||||
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
|
test "returns the activity that created an object" do
|
||||||
activity = insert(:note_activity)
|
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
|
assert activity == found_activity
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,44 +7,56 @@ defmodule Pleroma.FormatterTest do
|
||||||
describe ".add_hashtag_links" do
|
describe ".add_hashtag_links" do
|
||||||
test "turns hashtags into links" do
|
test "turns hashtags into links" do
|
||||||
text = "I love #cofe and #2hu"
|
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)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
describe ".add_links" do
|
describe ".add_links" do
|
||||||
test "turning urls into 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."
|
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"
|
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"
|
text = "@lambadalambda"
|
||||||
expected = "@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/"
|
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>"
|
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"
|
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"
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -60,9 +72,14 @@ test "gives a replacement for user links" do
|
||||||
{subs, text} = Formatter.add_user_links({[], text}, mentions)
|
{subs, text} = Formatter.add_user_links({[], text}, mentions)
|
||||||
|
|
||||||
assert length(subs) == 3
|
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})
|
assert expected_text == Formatter.finalize({subs, text})
|
||||||
end
|
end
|
||||||
|
@ -71,6 +88,7 @@ test "gives a replacement for user links" do
|
||||||
describe ".parse_tags" do
|
describe ".parse_tags" do
|
||||||
test "parses tags in the text" do
|
test "parses tags in the text" do
|
||||||
text = "Here's a #Test. Maybe these are #working or not. What about #漢字? And #は。"
|
text = "Here's a #Test. Maybe these are #working or not. What about #漢字? And #は。"
|
||||||
|
|
||||||
expected = [
|
expected = [
|
||||||
{"#Test", "test"},
|
{"#Test", "test"},
|
||||||
{"#working", "working"},
|
{"#working", "working"},
|
||||||
|
@ -92,7 +110,7 @@ test "it can parse mentions and return the relevant users" do
|
||||||
expected_result = [
|
expected_result = [
|
||||||
{"@gsimg", gsimg},
|
{"@gsimg", gsimg},
|
||||||
{"@archaeme", archaeme},
|
{"@archaeme", archaeme},
|
||||||
{"@archaeme@archae.me", archaeme_remote},
|
{"@archaeme@archae.me", archaeme_remote}
|
||||||
]
|
]
|
||||||
|
|
||||||
assert Formatter.parse_mentions(text) == expected_result
|
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
|
test "it adds cool emoji" do
|
||||||
text = "I love :moominmamma:"
|
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
|
assert Formatter.emojify(text) == expected_result
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,10 @@ test "notifies someone when they are directly addressed" do
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
third_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)
|
{: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)
|
user = insert(:user)
|
||||||
other_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.create_notifications(activity)
|
||||||
{:ok, notification} = Notification.get(other_user, notification.id)
|
{: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)
|
user = insert(:user)
|
||||||
other_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.create_notifications(activity)
|
||||||
{:error, _notification} = Notification.get(user, notification.id)
|
{:error, _notification} = Notification.get(user, notification.id)
|
||||||
end
|
end
|
||||||
|
@ -59,7 +66,9 @@ test "it dismisses a notification that belongs to the user" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_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.create_notifications(activity)
|
||||||
{:ok, notification} = Notification.dismiss(other_user, notification.id)
|
{: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)
|
user = insert(:user)
|
||||||
other_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.create_notifications(activity)
|
||||||
{:error, _notification} = Notification.dismiss(user, notification.id)
|
{:error, _notification} = Notification.dismiss(user, notification.id)
|
||||||
end
|
end
|
||||||
|
@ -82,9 +93,18 @@ test "it clears all notifications belonging to the user" do
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
third_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, _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)
|
{:ok, _notifs} = Notification.create_notifications(activity)
|
||||||
Notification.clear(other_user)
|
Notification.clear(other_user)
|
||||||
|
|
||||||
|
|
|
@ -37,22 +37,24 @@ defp basic_auth_enc(username, password) do
|
||||||
|
|
||||||
describe "without an authorization header" do
|
describe "without an authorization header" do
|
||||||
test "it halts the application" do
|
test "it halts the application" do
|
||||||
conn = build_conn()
|
conn =
|
||||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
build_conn()
|
||||||
|> fetch_session
|
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||||
|> AuthenticationPlug.call(%{})
|
|> fetch_session
|
||||||
|
|> AuthenticationPlug.call(%{})
|
||||||
|
|
||||||
assert conn.status == 403
|
assert conn.status == 403
|
||||||
assert conn.halted == true
|
assert conn.halted == true
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it assigns a nil user if the 'optional' option is used" do
|
test "it assigns a nil user if the 'optional' option is used" do
|
||||||
conn = build_conn()
|
conn =
|
||||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
build_conn()
|
||||||
|> fetch_session
|
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||||
|> AuthenticationPlug.call(%{optional: true})
|
|> fetch_session
|
||||||
|
|> AuthenticationPlug.call(%{optional: true})
|
||||||
|
|
||||||
assert %{ user: nil } == conn.assigns
|
assert %{user: nil} == conn.assigns
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -73,9 +75,9 @@ test "it assigns a nil user if the 'optional' option is used" do
|
||||||
build_conn()
|
build_conn()
|
||||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||||
|> fetch_session
|
|> 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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -113,7 +115,7 @@ test "it assigns a nil user if the 'optional' option is used" do
|
||||||
|> put_req_header("authorization", header)
|
|> put_req_header("authorization", header)
|
||||||
|> AuthenticationPlug.call(opts)
|
|> AuthenticationPlug.call(opts)
|
||||||
|
|
||||||
assert %{ user: nil } == conn.assigns
|
assert %{user: nil} == conn.assigns
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -126,13 +128,14 @@ test "it assigns the user", %{conn: conn} do
|
||||||
|
|
||||||
header = basic_auth_enc("dude", "guy")
|
header = basic_auth_enc("dude", "guy")
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|
conn
|
||||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||||
|> fetch_session
|
|> fetch_session
|
||||||
|> put_req_header("authorization", header)
|
|> put_req_header("authorization", header)
|
||||||
|> AuthenticationPlug.call(opts)
|
|> AuthenticationPlug.call(opts)
|
||||||
|
|
||||||
assert %{ user: @user } == conn.assigns
|
assert %{user: @user} == conn.assigns
|
||||||
assert get_session(conn, :user_id) == @user.id
|
assert get_session(conn, :user_id) == @user.id
|
||||||
assert conn.halted == false
|
assert conn.halted == false
|
||||||
end
|
end
|
||||||
|
@ -147,7 +150,8 @@ test "it halts the appication", %{conn: conn} do
|
||||||
|
|
||||||
header = basic_auth_enc("dude", "guy")
|
header = basic_auth_enc("dude", "guy")
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|
conn
|
||||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||||
|> fetch_session
|
|> fetch_session
|
||||||
|> put_req_header("authorization", header)
|
|> 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")
|
header = basic_auth_enc("dude", "THIS IS WRONG")
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|
conn
|
||||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||||
|> fetch_session
|
|> fetch_session
|
||||||
|> put_session(:user_id, @user.id)
|
|> put_session(:user_id, @user.id)
|
||||||
|> put_req_header("authorization", header)
|
|> put_req_header("authorization", header)
|
||||||
|> AuthenticationPlug.call(opts)
|
|> AuthenticationPlug.call(opts)
|
||||||
|
|
||||||
assert %{ user: @user } == conn.assigns
|
assert %{user: @user} == conn.assigns
|
||||||
assert get_session(conn, :user_id) == @user.id
|
assert get_session(conn, :user_id) == @user.id
|
||||||
assert conn.halted == false
|
assert conn.halted == false
|
||||||
end
|
end
|
||||||
|
@ -182,8 +187,9 @@ test "it assigns the user", %{conn: conn} do
|
||||||
|
|
||||||
describe "with an assigned user" do
|
describe "with an assigned user" do
|
||||||
test "it does nothing, returning the incoming conn", %{conn: conn} do
|
test "it does nothing, returning the incoming conn", %{conn: conn} do
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, @user)
|
conn
|
||||||
|
|> assign(:user, @user)
|
||||||
|
|
||||||
conn_result = AuthenticationPlug.call(conn, %{})
|
conn_result = AuthenticationPlug.call(conn, %{})
|
||||||
|
|
||||||
|
|
|
@ -4,17 +4,19 @@ defmodule Pleroma.Builders.ActivityBuilder do
|
||||||
|
|
||||||
def build(data \\ %{}, opts \\ %{}) do
|
def build(data \\ %{}, opts \\ %{}) do
|
||||||
user = opts[:user] || Pleroma.Factory.insert(:user)
|
user = opts[:user] || Pleroma.Factory.insert(:user)
|
||||||
|
|
||||||
activity = %{
|
activity = %{
|
||||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_object_id,
|
"id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
|
||||||
"actor" => user.ap_id,
|
"actor" => user.ap_id,
|
||||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
"object" => %{
|
"object" => %{
|
||||||
"type" => "Note",
|
"type" => "Note",
|
||||||
"content" => "test",
|
"content" => "test",
|
||||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
"to" => ["https://www.w3.org/ns/activitystreams#Public"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map.merge(activity, data)
|
Map.merge(activity, data)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -24,7 +26,7 @@ def insert(data \\ %{}, opts \\ %{}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def insert_list(times, data \\ %{}, opts \\ %{}) do
|
def insert_list(times, data \\ %{}, opts \\ %{}) do
|
||||||
Enum.map(1..times, fn (n) ->
|
Enum.map(1..times, fn n ->
|
||||||
{:ok, activity} = insert(data, opts)
|
{:ok, activity} = insert(data, opts)
|
||||||
activity
|
activity
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -10,6 +10,7 @@ def build(data \\ %{}) do
|
||||||
bio: "A tester.",
|
bio: "A tester.",
|
||||||
ap_id: "some id"
|
ap_id: "some id"
|
||||||
}
|
}
|
||||||
|
|
||||||
Map.merge(user, data)
|
Map.merge(user, data)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -25,13 +25,13 @@ defmodule Pleroma.Web.ChannelCase do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
setup tags do
|
setup tags do
|
||||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
|
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
|
||||||
|
|
||||||
unless tags[:async] do
|
unless tags[:async] do
|
||||||
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, {:shared, self()})
|
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, {:shared, self()})
|
||||||
end
|
end
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,14 +26,14 @@ defmodule Pleroma.Web.ConnCase do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
setup tags do
|
setup tags do
|
||||||
Cachex.clear(:user_cache)
|
Cachex.clear(:user_cache)
|
||||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
|
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
|
||||||
|
|
||||||
unless tags[:async] do
|
unless tags[:async] do
|
||||||
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, {:shared, self()})
|
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, {:shared, self()})
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, conn: Phoenix.ConnTest.build_conn()}
|
{:ok, conn: Phoenix.ConnTest.build_conn()}
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,20 +9,27 @@ def user_factory do
|
||||||
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
|
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
|
||||||
bio: sequence(:bio, &"Tester Number #{&1}")
|
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
|
end
|
||||||
|
|
||||||
def note_factory do
|
def note_factory do
|
||||||
text = sequence(:text, &"This is :moominmamma: note #{&1}")
|
text = sequence(:text, &"This is :moominmamma: note #{&1}")
|
||||||
|
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"type" => "Note",
|
"type" => "Note",
|
||||||
"content" => text,
|
"content" => text,
|
||||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_object_id,
|
"id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
|
||||||
"actor" => user.ap_id,
|
"actor" => user.ap_id,
|
||||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
"published" => DateTime.utc_now() |> DateTime.to_iso8601,
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||||
"likes" => [],
|
"likes" => [],
|
||||||
"like_count" => 0,
|
"like_count" => 0,
|
||||||
"context" => "2hu",
|
"context" => "2hu",
|
||||||
|
@ -40,13 +47,14 @@ def note_factory do
|
||||||
|
|
||||||
def note_activity_factory do
|
def note_activity_factory do
|
||||||
note = insert(:note)
|
note = insert(:note)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id,
|
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
"actor" => note.data["actor"],
|
"actor" => note.data["actor"],
|
||||||
"to" => note.data["to"],
|
"to" => note.data["to"],
|
||||||
"object" => note.data,
|
"object" => note.data,
|
||||||
"published" => DateTime.utc_now() |> DateTime.to_iso8601,
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||||
"context" => note.data["context"]
|
"context" => note.data["context"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,11 +70,11 @@ def like_activity_factory do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id,
|
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||||
"actor" => user.ap_id,
|
"actor" => user.ap_id,
|
||||||
"type" => "Like",
|
"type" => "Like",
|
||||||
"object" => note_activity.data["object"]["id"],
|
"object" => note_activity.data["object"]["id"],
|
||||||
"published_at" => DateTime.utc_now() |> DateTime.to_iso8601
|
"published_at" => DateTime.utc_now() |> DateTime.to_iso8601()
|
||||||
}
|
}
|
||||||
|
|
||||||
%Pleroma.Activity{
|
%Pleroma.Activity{
|
||||||
|
@ -79,11 +87,11 @@ def follow_activity_factory do
|
||||||
followed = insert(:user)
|
followed = insert(:user)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id,
|
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||||
"actor" => follower.ap_id,
|
"actor" => follower.ap_id,
|
||||||
"type" => "Follow",
|
"type" => "Follow",
|
||||||
"object" => followed.ap_id,
|
"object" => followed.ap_id,
|
||||||
"published_at" => DateTime.utc_now() |> DateTime.to_iso8601
|
"published_at" => DateTime.utc_now() |> DateTime.to_iso8601()
|
||||||
}
|
}
|
||||||
|
|
||||||
%Pleroma.Activity{
|
%Pleroma.Activity{
|
||||||
|
@ -96,7 +104,7 @@ def websub_subscription_factory do
|
||||||
topic: "http://example.org",
|
topic: "http://example.org",
|
||||||
callback: "http://example/org/callback",
|
callback: "http://example/org/callback",
|
||||||
secret: "here's a secret",
|
secret: "here's a secret",
|
||||||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now, 100),
|
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 100),
|
||||||
state: "requested"
|
state: "requested"
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,6 @@
|
||||||
defmodule Pleroma.Web.OStatusMock do
|
defmodule Pleroma.Web.OStatusMock do
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
def handle_incoming(_doc) do
|
def handle_incoming(_doc) do
|
||||||
insert(:note_activity)
|
insert(:note_activity)
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,4 +2,3 @@
|
||||||
|
|
||||||
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
|
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
|
||||||
{:ok, _} = Application.ensure_all_started(:ex_machina)
|
{:ok, _} = Application.ensure_all_started(:ex_machina)
|
||||||
|
|
||||||
|
|
|
@ -4,20 +4,37 @@ defmodule Pleroma.UploadTest do
|
||||||
|
|
||||||
describe "Storing a file" do
|
describe "Storing a file" do
|
||||||
test "copies the file to the configured folder" 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)
|
data = Upload.store(file)
|
||||||
assert data["name"] == "an [image.jpg"
|
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
|
end
|
||||||
|
|
||||||
test "fixes an incorrect content type" do
|
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)
|
data = Upload.store(file)
|
||||||
assert hd(data["url"])["mediaType"] == "image/jpeg"
|
assert hd(data["url"])["mediaType"] == "image/jpeg"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "does not modify a valid content type" do
|
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)
|
data = Upload.store(file)
|
||||||
assert hd(data["url"])["mediaType"] == "image/png"
|
assert hd(data["url"])["mediaType"] == "image/png"
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,15 +10,15 @@ defmodule Pleroma.UserTest do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
test "ap_id returns the activity pub id for the user" do
|
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)
|
assert expected_ap_id == User.ap_id(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "ap_followers returns the followers collection for the user" do
|
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"
|
expected_followers_collection = "#{User.ap_id(user)}/followers"
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ test "unfollow takes a user and another user" do
|
||||||
followed = insert(:user)
|
followed = insert(:user)
|
||||||
user = insert(:user, %{following: [User.ap_followers(followed)]})
|
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)
|
user = Repo.get(User, user.id)
|
||||||
|
|
||||||
|
@ -83,7 +83,6 @@ test "unfollow doesn't unfollow yourself" do
|
||||||
assert user.following == [user.ap_id]
|
assert user.following == [user.ap_id]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
test "test if a user is following another user" do
|
test "test if a user is following another user" do
|
||||||
followed = insert(:user)
|
followed = insert(:user)
|
||||||
user = insert(:user, %{following: [User.ap_followers(followed)]})
|
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
|
test "it requires an email, name, nickname and password, bio is optional" do
|
||||||
@full_user_data
|
@full_user_data
|
||||||
|> Map.keys
|
|> Map.keys()
|
||||||
|> Enum.each(fn (key) ->
|
|> Enum.each(fn key ->
|
||||||
params = Map.delete(@full_user_data, key)
|
params = Map.delete(@full_user_data, key)
|
||||||
changeset = User.register_changeset(%User{}, params)
|
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)
|
||||||
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 is_binary(changeset.changes[:password_hash])
|
||||||
assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname})
|
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"
|
assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -158,12 +161,24 @@ test "returns nil for nonexistant local user" do
|
||||||
|
|
||||||
test "returns an ap_id for a user" do
|
test "returns an ap_id for a user" do
|
||||||
user = insert(:user)
|
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
|
end
|
||||||
|
|
||||||
test "returns an ap_followers link for a user" do
|
test "returns an ap_followers link for a user" do
|
||||||
user = insert(:user)
|
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
|
end
|
||||||
|
|
||||||
describe "remote user creation changeset" do
|
describe "remote user creation changeset" do
|
||||||
|
@ -184,7 +199,8 @@ test "it confirms validity" do
|
||||||
test "it sets the follower_adress" do
|
test "it sets the follower_adress" do
|
||||||
cs = User.remote_user_creation(@valid_remote)
|
cs = User.remote_user_creation(@valid_remote)
|
||||||
# remote users get a fake local follower address
|
# 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
|
end
|
||||||
|
|
||||||
test "it enforces the fqn format for nicknames" do
|
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
|
test "it has required fields" do
|
||||||
[:name, :nickname, :ap_id]
|
[:name, :nickname, :ap_id]
|
||||||
|> Enum.each(fn (field) ->
|
|> Enum.each(fn field ->
|
||||||
cs = User.remote_user_creation(Map.delete(@valid_remote, field))
|
cs = User.remote_user_creation(Map.delete(@valid_remote, field))
|
||||||
refute cs.valid?
|
refute cs.valid?
|
||||||
end)
|
end)
|
||||||
|
@ -204,7 +220,7 @@ test "it has required fields" do
|
||||||
|
|
||||||
test "it restricts some sizes" do
|
test "it restricts some sizes" do
|
||||||
[bio: 5000, name: 100]
|
[bio: 5000, name: 100]
|
||||||
|> Enum.each(fn ({field, size}) ->
|
|> Enum.each(fn {field, size} ->
|
||||||
string = String.pad_leading(".", size)
|
string = String.pad_leading(".", size)
|
||||||
cs = User.remote_user_creation(Map.put(@valid_remote, field, string))
|
cs = User.remote_user_creation(Map.put(@valid_remote, field, string))
|
||||||
assert cs.valid?
|
assert cs.valid?
|
||||||
|
@ -323,7 +339,11 @@ test "get recipients from activity" do
|
||||||
user_two = insert(:user, local: false)
|
user_two = insert(:user, local: false)
|
||||||
addressed = insert(:user, local: true)
|
addressed = insert(:user, local: true)
|
||||||
addressed_remote = insert(:user, local: false)
|
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)
|
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
|
test "insert or update a user from given data" do
|
||||||
user = insert(:user, %{nickname: "nick@name.de"})
|
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)
|
assert {:ok, %User{}} = User.insert_or_update_user(data)
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,9 +9,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
test "it returns a json representation of the user", %{conn: conn} do
|
test "it returns a json representation of the user", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> put_req_header("accept", "application/activity+json")
|
conn
|
||||||
|> get("/users/#{user.nickname}")
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|
|> get("/users/#{user.nickname}")
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
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
|
describe "/object/:uuid" do
|
||||||
test "it returns a json representation of the object", %{conn: conn} do
|
test "it returns a json representation of the object", %{conn: conn} do
|
||||||
note = insert(:note)
|
note = insert(:note)
|
||||||
uuid = String.split(note.data["id"], "/") |> List.last
|
uuid = String.split(note.data["id"], "/") |> List.last()
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> put_req_header("accept", "application/activity+json")
|
conn
|
||||||
|> get("/objects/#{uuid}")
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|
|> get("/objects/#{uuid}")
|
||||||
|
|
||||||
assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
|
assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
|
||||||
end
|
end
|
||||||
|
@ -34,12 +36,13 @@ test "it returns a json representation of the object", %{conn: conn} do
|
||||||
|
|
||||||
describe "/users/:nickname/inbox" do
|
describe "/users/:nickname/inbox" do
|
||||||
test "it inserts an incoming activity into the database", %{conn: conn} 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
|
conn =
|
||||||
|> assign(:valid_signature, true)
|
conn
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> assign(:valid_signature, true)
|
||||||
|> post("/inbox", data)
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|
|> post("/inbox", data)
|
||||||
|
|
||||||
assert "ok" == json_response(conn, 200)
|
assert "ok" == json_response(conn, 200)
|
||||||
:timer.sleep(500)
|
:timer.sleep(500)
|
||||||
|
|
|
@ -22,7 +22,7 @@ test "it returns a user" do
|
||||||
describe "insertion" do
|
describe "insertion" do
|
||||||
test "returns the activity if one with the same id is already in" do
|
test "returns the activity if one with the same id is already in" do
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
{:ok, new_activity}= ActivityPub.insert(activity.data)
|
{:ok, new_activity} = ActivityPub.insert(activity.data)
|
||||||
|
|
||||||
assert activity == new_activity
|
assert activity == new_activity
|
||||||
end
|
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"])
|
assert is_binary(activity.data["id"])
|
||||||
|
|
||||||
given_id = "bla"
|
given_id = "bla"
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"ok" => true,
|
"ok" => true,
|
||||||
"id" => given_id
|
"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
|
describe "create activities" do
|
||||||
test "removes doubled 'to' recipients" 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.data["to"] == ["user1", "user2"]
|
||||||
assert activity.actor == "1"
|
assert activity.actor == "1"
|
||||||
assert activity.recipients == ["user1", "user2"]
|
assert activity.recipients == ["user1", "user2"]
|
||||||
|
@ -124,11 +132,11 @@ test "doesn't return blocked activities" do
|
||||||
|
|
||||||
describe "public fetch activities" do
|
describe "public fetch activities" do
|
||||||
test "retrieves public 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 length(activities) == 1
|
||||||
assert Enum.at(activities, 0) == public
|
assert Enum.at(activities, 0) == public
|
||||||
end
|
end
|
||||||
|
@ -137,7 +145,7 @@ test "retrieves a maximum of 20 activities" do
|
||||||
activities = ActivityBuilder.insert_list(30)
|
activities = ActivityBuilder.insert_list(30)
|
||||||
last_expected = List.last(activities)
|
last_expected = List.last(activities)
|
||||||
|
|
||||||
activities = ActivityPub.fetch_public_activities
|
activities = ActivityPub.fetch_public_activities()
|
||||||
last = List.last(activities)
|
last = List.last(activities)
|
||||||
|
|
||||||
assert length(activities) == 20
|
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)
|
{:ok, announce_activity, object} = ActivityPub.announce(user, object)
|
||||||
assert object.data["announcement_count"] == 1
|
assert object.data["announcement_count"] == 1
|
||||||
assert object.data["announcements"] == [user.ap_id]
|
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["object"] == object.data["id"]
|
||||||
assert announce_activity.data["actor"] == user.ap_id
|
assert announce_activity.data["actor"] == user.ap_id
|
||||||
assert announce_activity.data["context"] == object.data["context"]
|
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
|
describe "uploading files" do
|
||||||
test "copies the file to the configured folder" 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)
|
{:ok, %Object{} = object} = ActivityPub.upload(file)
|
||||||
assert object.data["name"] == "an_image.jpg"
|
assert object.data["name"] == "an_image.jpg"
|
||||||
|
@ -268,11 +285,14 @@ test "fetches the latest Follow activity" do
|
||||||
|
|
||||||
describe "fetching an object" do
|
describe "fetching an object" do
|
||||||
test "it fetches 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 = Activity.get_create_activity_by_object_ap_id(object.data["id"])
|
||||||
assert activity.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 [attachment] = object.data["attachment"]
|
||||||
assert is_list(attachment["url"])
|
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 = Activity.get_create_activity_by_object_ap_id(object.data["id"])
|
||||||
assert activity.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
|
assert object == object_again
|
||||||
end
|
end
|
||||||
|
@ -344,7 +365,14 @@ test "it creates an update activity with the new user data" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
|
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
|
||||||
user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: 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["actor"] == user.ap_id
|
||||||
assert update.data["to"] == [user.follower_address]
|
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
|
test "it ignores an incoming notice if we already have it" do
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
|
|
||||||
data = File.read!("test/fixtures/mastodon-post-activity.json")
|
data =
|
||||||
|> Poison.decode!
|
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||||
|> Map.put("object", activity.data["object"])
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", activity.data["object"])
|
||||||
|
|
||||||
{:ok, returned_activity} = Transmogrifier.handle_incoming(data)
|
{:ok, returned_activity} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
@ -26,51 +27,72 @@ test "it ignores an incoming notice if we already have it" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it fetches replied-to activities if we don't have them" do
|
test "it fetches replied-to activities if we don't have them" do
|
||||||
data = File.read!("test/fixtures/mastodon-post-activity.json")
|
data =
|
||||||
|> Poison.decode!
|
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|
||||||
object = data["object"]
|
object =
|
||||||
|> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
|
data["object"]
|
||||||
|
|> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
|
||||||
|
|
||||||
data = data
|
data =
|
||||||
|> Map.put("object", object)
|
data
|
||||||
|
|> Map.put("object", object)
|
||||||
|
|
||||||
{:ok, returned_activity} = Transmogrifier.handle_incoming(data)
|
{: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 activity =
|
||||||
assert returned_activity.data["object"]["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
|
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
|
assert returned_activity.data["object"]["inReplyToStatusId"] == activity.id
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming notices" do
|
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)
|
{: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["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
||||||
|
|
||||||
assert data["cc"] == [
|
assert data["cc"] == [
|
||||||
"http://mastodon.example.org/users/admin/followers",
|
"http://mastodon.example.org/users/admin/followers",
|
||||||
"http://localtesting.pleroma.lol/users/lain"
|
"http://localtesting.pleroma.lol/users/lain"
|
||||||
]
|
]
|
||||||
|
|
||||||
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||||
|
|
||||||
object = data["object"]
|
object = data["object"]
|
||||||
assert object["id"] == "http://mastodon.example.org/users/admin/statuses/99512778738411822"
|
assert object["id"] == "http://mastodon.example.org/users/admin/statuses/99512778738411822"
|
||||||
|
|
||||||
assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
||||||
|
|
||||||
assert object["cc"] == [
|
assert object["cc"] == [
|
||||||
"http://mastodon.example.org/users/admin/followers",
|
"http://mastodon.example.org/users/admin/followers",
|
||||||
"http://localtesting.pleroma.lol/users/lain"
|
"http://localtesting.pleroma.lol/users/lain"
|
||||||
]
|
]
|
||||||
|
|
||||||
assert object["actor"] == "http://mastodon.example.org/users/admin"
|
assert object["actor"] == "http://mastodon.example.org/users/admin"
|
||||||
assert object["attributedTo"] == "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
|
assert object["sensitive"] == true
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming notices with hashtags" do
|
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)
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
assert Enum.at(data["object"]["tag"], 2) == "moo"
|
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
|
test "it works for incoming follow requests" do
|
||||||
user = insert(:user)
|
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)
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
@ -93,8 +117,9 @@ test "it works for incoming likes" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
||||||
|
|
||||||
data = File.read!("test/fixtures/mastodon-like.json") |> Poison.decode!
|
data =
|
||||||
|> Map.put("object", activity.data["object"]["id"])
|
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)
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
@ -105,14 +130,18 @@ test "it works for incoming likes" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming announces" do
|
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)
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||||
assert data["type"] == "Announce"
|
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"])
|
assert Activity.get_create_activity_by_object_ap_id(data["object"])
|
||||||
end
|
end
|
||||||
|
@ -121,53 +150,77 @@ test "it works for incoming announces with an existing activity" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
|
||||||
|
|
||||||
data = File.read!("test/fixtures/mastodon-announce.json")
|
data =
|
||||||
|> Poison.decode!
|
File.read!("test/fixtures/mastodon-announce.json")
|
||||||
|> Map.put("object", activity.data["object"]["id"])
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", activity.data["object"]["id"])
|
||||||
|
|
||||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||||
assert data["type"] == "Announce"
|
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 data["object"] == activity.data["object"]["id"]
|
||||||
|
|
||||||
assert Activity.get_create_activity_by_object_ap_id(data["object"]).id == activity.id
|
assert Activity.get_create_activity_by_object_ap_id(data["object"]).id == activity.id
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming update activities" do
|
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)
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!
|
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 = update_data
|
object =
|
||||||
|> Map.put("actor", data["actor"])
|
update_data["object"]
|
||||||
|> Map.put("object", 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)
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
|
||||||
|
|
||||||
user = User.get_cached_by_ap_id(data["actor"])
|
user = User.get_cached_by_ap_id(data["actor"])
|
||||||
assert user.name == "gargle"
|
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>"
|
assert user.bio == "<p>Some bio</p>"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming deletes" do
|
test "it works for incoming deletes" do
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
data = File.read!("test/fixtures/mastodon-delete.json")
|
|
||||||
|> Poison.decode!
|
|
||||||
|
|
||||||
object = data["object"]
|
data =
|
||||||
|> Map.put("id", activity.data["object"]["id"])
|
File.read!("test/fixtures/mastodon-delete.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|
||||||
data = data
|
object =
|
||||||
|> Map.put("object", object)
|
data["object"]
|
||||||
|> Map.put("actor", activity.data["actor"])
|
|> 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)
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
@ -180,7 +233,8 @@ test "it turns mentions into tags" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_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)
|
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
object = modified["object"]
|
object = modified["object"]
|
||||||
|
@ -192,7 +246,7 @@ test "it turns mentions into tags" do
|
||||||
}
|
}
|
||||||
|
|
||||||
expected_tag = %{
|
expected_tag = %{
|
||||||
"href" => Pleroma.Web.Endpoint.url <> "/tags/2hu",
|
"href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
|
||||||
"type" => "Hashtag",
|
"type" => "Hashtag",
|
||||||
"name" => "#2hu"
|
"name" => "#2hu"
|
||||||
}
|
}
|
||||||
|
@ -247,7 +301,9 @@ test "it translates ostatus reply_to IDs to external URLs" do
|
||||||
|
|
||||||
user = insert(:user)
|
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)
|
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
|
|
||||||
assert modified["object"]["inReplyTo"] == "http://gs.example.org:4040/index.php/notice/29"
|
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
|
describe "user upgrade" do
|
||||||
test "it upgrades a user to activitypub" 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]})
|
user_two = insert(:user, %{following: [user.follower_address]})
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
|
{: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)
|
activity = Repo.get(Activity, activity.id)
|
||||||
assert user.follower_address in activity.recipients
|
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
|
refute "..." in activity.recipients
|
||||||
|
|
||||||
unrelated_activity = Repo.get(Activity, unrelated_activity.id)
|
unrelated_activity = Repo.get(Activity, unrelated_activity.id)
|
||||||
|
|
|
@ -3,7 +3,8 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
test "it adds attachment links to a given text and attachment set" do
|
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 = %{
|
attachment = %{
|
||||||
"url" => [%{"href" => name}]
|
"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])
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
test "Represent a user account" do
|
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 = %{
|
expected = %{
|
||||||
id: to_string(user.id),
|
id: to_string(user.id),
|
||||||
|
|
|
@ -14,17 +14,19 @@ test "the home timeline", %{conn: conn} do
|
||||||
|
|
||||||
{:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
|
{:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> get("/api/v1/timelines/home")
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/timelines/home")
|
||||||
|
|
||||||
assert length(json_response(conn, 200)) == 0
|
assert length(json_response(conn, 200)) == 0
|
||||||
|
|
||||||
{:ok, user} = User.follow(user, following)
|
{:ok, user} = User.follow(user, following)
|
||||||
|
|
||||||
conn = build_conn()
|
conn =
|
||||||
|> assign(:user, user)
|
build_conn()
|
||||||
|> get("/api/v1/timelines/home")
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/timelines/home")
|
||||||
|
|
||||||
assert [%{"content" => "test"}] = json_response(conn, 200)
|
assert [%{"content" => "test"}] = json_response(conn, 200)
|
||||||
end
|
end
|
||||||
|
@ -32,44 +34,57 @@ test "the home timeline", %{conn: conn} do
|
||||||
test "the public timeline", %{conn: conn} do
|
test "the public timeline", %{conn: conn} do
|
||||||
following = insert(:user)
|
following = insert(:user)
|
||||||
|
|
||||||
capture_log fn ->
|
capture_log(fn ->
|
||||||
{:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
|
{:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
|
||||||
{:ok, [_activity]} = OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
|
|
||||||
|
|
||||||
conn = conn
|
{:ok, [_activity]} =
|
||||||
|> get("/api/v1/timelines/public", %{"local" => "False"})
|
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
|
assert length(json_response(conn, 200)) == 2
|
||||||
|
|
||||||
conn = build_conn()
|
conn =
|
||||||
|> get("/api/v1/timelines/public", %{"local" => "True"})
|
build_conn()
|
||||||
|
|> get("/api/v1/timelines/public", %{"local" => "True"})
|
||||||
|
|
||||||
assert [%{"content" => "test"}] = json_response(conn, 200)
|
assert [%{"content" => "test"}] = json_response(conn, 200)
|
||||||
|
|
||||||
conn = build_conn()
|
conn =
|
||||||
|> get("/api/v1/timelines/public", %{"local" => "1"})
|
build_conn()
|
||||||
|
|> get("/api/v1/timelines/public", %{"local" => "1"})
|
||||||
|
|
||||||
assert [%{"content" => "test"}] = json_response(conn, 200)
|
assert [%{"content" => "test"}] = json_response(conn, 200)
|
||||||
end
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "posting a status", %{conn: conn} do
|
test "posting a status", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> post("/api/v1/statuses", %{"status" => "cofe", "spoiler_text" => "2hu", "sensitive" => "false"})
|
|> 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)
|
assert Repo.get(Activity, id)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "posting a sensitive status", %{conn: conn} do
|
test "posting a sensitive status", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
|
||||||
|
|
||||||
assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
|
assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
|
||||||
assert Repo.get(Activity, id)
|
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"})
|
{:ok, replied_to} = TwitterAPI.create_status(user, %{"status" => "cofe"})
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
|
|> 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)
|
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
|
test "verify_credentials", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> get("/api/v1/accounts/verify_credentials")
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/accounts/verify_credentials")
|
||||||
|
|
||||||
assert %{"id" => id} = json_response(conn, 200)
|
assert %{"id" => id} = json_response(conn, 200)
|
||||||
assert id == to_string(user.id)
|
assert id == to_string(user.id)
|
||||||
|
@ -106,8 +123,9 @@ test "verify_credentials", %{conn: conn} do
|
||||||
test "get a status", %{conn: conn} do
|
test "get a status", %{conn: conn} do
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> get("/api/v1/statuses/#{activity.id}")
|
conn
|
||||||
|
|> get("/api/v1/statuses/#{activity.id}")
|
||||||
|
|
||||||
assert %{"id" => id} = json_response(conn, 200)
|
assert %{"id" => id} = json_response(conn, 200)
|
||||||
assert id == to_string(activity.id)
|
assert id == to_string(activity.id)
|
||||||
|
@ -118,9 +136,10 @@ test "when you created it", %{conn: conn} do
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
author = User.get_by_ap_id(activity.data["actor"])
|
author = User.get_by_ap_id(activity.data["actor"])
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, author)
|
conn
|
||||||
|> delete("/api/v1/statuses/#{activity.id}")
|
|> assign(:user, author)
|
||||||
|
|> delete("/api/v1/statuses/#{activity.id}")
|
||||||
|
|
||||||
assert %{} = json_response(conn, 200)
|
assert %{} = json_response(conn, 200)
|
||||||
|
|
||||||
|
@ -131,9 +150,10 @@ test "when you didn't create it", %{conn: conn} do
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> delete("/api/v1/statuses/#{activity.id}")
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/statuses/#{activity.id}")
|
||||||
|
|
||||||
assert %{"error" => _} = json_response(conn, 403)
|
assert %{"error" => _} = json_response(conn, 403)
|
||||||
|
|
||||||
|
@ -146,14 +166,19 @@ test "list of notifications", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_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)
|
{:ok, [_notification]} = Notification.create_notifications(activity)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> get("/api/v1/notifications")
|
|> 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 [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
|
||||||
assert response == expected_response
|
assert response == expected_response
|
||||||
end
|
end
|
||||||
|
@ -162,14 +187,19 @@ test "getting a single notification", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_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)
|
{:ok, [notification]} = Notification.create_notifications(activity)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> get("/api/v1/notifications/#{notification.id}")
|
|> 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 %{"status" => %{"content" => response}} = json_response(conn, 200)
|
||||||
assert response == expected_response
|
assert response == expected_response
|
||||||
end
|
end
|
||||||
|
@ -178,12 +208,15 @@ test "dismissing a single notification", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_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)
|
{:ok, [notification]} = Notification.create_notifications(activity)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
|
||||||
|
|
||||||
assert %{} = json_response(conn, 200)
|
assert %{} = json_response(conn, 200)
|
||||||
end
|
end
|
||||||
|
@ -192,18 +225,22 @@ test "clearing all notifications", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_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)
|
{:ok, [_notification]} = Notification.create_notifications(activity)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> post("/api/v1/notifications/clear")
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/notifications/clear")
|
||||||
|
|
||||||
assert %{} = json_response(conn, 200)
|
assert %{} = json_response(conn, 200)
|
||||||
|
|
||||||
conn = build_conn()
|
conn =
|
||||||
|> assign(:user, user)
|
build_conn()
|
||||||
|> get("/api/v1/notifications")
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/notifications")
|
||||||
|
|
||||||
assert all = json_response(conn, 200)
|
assert all = json_response(conn, 200)
|
||||||
assert all == []
|
assert all == []
|
||||||
|
@ -215,11 +252,14 @@ test "reblogs and returns the reblogged status", %{conn: conn} do
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> post("/api/v1/statuses/#{activity.id}/reblog")
|
|> 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
|
assert to_string(activity.id) == id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -229,11 +269,14 @@ test "favs a status and returns it", %{conn: conn} do
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> post("/api/v1/statuses/#{activity.id}/favourite")
|
|> 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
|
assert to_string(activity.id) == id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -245,11 +288,14 @@ test "unfavorites a status and returns it", %{conn: conn} do
|
||||||
|
|
||||||
{:ok, _, _} = CommonAPI.favorite(activity.id, user)
|
{:ok, _, _} = CommonAPI.favorite(activity.id, user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> post("/api/v1/statuses/#{activity.id}/unfavourite")
|
|> 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
|
assert to_string(activity.id) == id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -261,8 +307,9 @@ test "gets a users statuses", %{conn: conn} do
|
||||||
|
|
||||||
user = User.get_by_ap_id(note_two.data["actor"])
|
user = User.get_by_ap_id(note_two.data["actor"])
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> get("/api/v1/accounts/#{user.id}/statuses")
|
conn
|
||||||
|
|> get("/api/v1/accounts/#{user.id}/statuses")
|
||||||
|
|
||||||
assert [%{"id" => id}] = json_response(conn, 200)
|
assert [%{"id" => id}] = json_response(conn, 200)
|
||||||
|
|
||||||
|
@ -273,20 +320,29 @@ test "gets an users media", %{conn: conn} do
|
||||||
note = insert(:note_activity)
|
note = insert(:note_activity)
|
||||||
user = User.get_by_ap_id(note.data["actor"])
|
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"}
|
file = %Plug.Upload{
|
||||||
media = TwitterAPI.upload(file, "json")
|
content_type: "image/jpg",
|
||||||
|> Poison.decode!
|
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
|
{:ok, image_post} =
|
||||||
|> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
|
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" => id}] = json_response(conn, 200)
|
||||||
assert id == to_string(image_post.id)
|
assert id == to_string(image_post.id)
|
||||||
|
|
||||||
conn = build_conn()
|
conn =
|
||||||
|> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
|
build_conn()
|
||||||
|
|> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
|
||||||
|
|
||||||
assert [%{"id" => id}] = json_response(conn, 200)
|
assert [%{"id" => id}] = json_response(conn, 200)
|
||||||
assert id == to_string(image_post.id)
|
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)
|
other_user = insert(:user)
|
||||||
{:ok, user} = User.follow(user, other_user)
|
{:ok, user} = User.follow(user, other_user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
|
||||||
|
|
||||||
assert [relationship] = json_response(conn, 200)
|
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
|
test "account fetching", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> get("/api/v1/accounts/#{user.id}")
|
conn
|
||||||
|
|> get("/api/v1/accounts/#{user.id}")
|
||||||
|
|
||||||
assert %{"id" => id} = json_response(conn, 200)
|
assert %{"id" => id} = json_response(conn, 200)
|
||||||
assert id == to_string(user.id)
|
assert id == to_string(user.id)
|
||||||
|
|
||||||
conn = build_conn()
|
conn =
|
||||||
|> get("/api/v1/accounts/-1")
|
build_conn()
|
||||||
|
|> get("/api/v1/accounts/-1")
|
||||||
|
|
||||||
assert %{"error" => "Can't find user"} = json_response(conn, 404)
|
assert %{"error" => "Can't find user"} = json_response(conn, 404)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "media upload", %{conn: conn} do
|
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)
|
user = insert(:user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> post("/api/v1/media", %{"file" => file})
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/media", %{"file" => file})
|
||||||
|
|
||||||
assert media = json_response(conn, 200)
|
assert media = json_response(conn, 200)
|
||||||
|
|
||||||
|
@ -341,16 +405,20 @@ test "media upload", %{conn: conn} do
|
||||||
test "hashtag timeline", %{conn: conn} do
|
test "hashtag timeline", %{conn: conn} do
|
||||||
following = insert(:user)
|
following = insert(:user)
|
||||||
|
|
||||||
capture_log fn ->
|
capture_log(fn ->
|
||||||
{:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"})
|
{:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"})
|
||||||
{:ok, [_activity]} = OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
|
|
||||||
conn = conn
|
{:ok, [_activity]} =
|
||||||
|> get("/api/v1/timelines/tag/2hu")
|
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" => id}] = json_response(conn, 200)
|
||||||
|
|
||||||
assert id == to_string(activity.id)
|
assert id == to_string(activity.id)
|
||||||
end
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "getting followers", %{conn: conn} do
|
test "getting followers", %{conn: conn} do
|
||||||
|
@ -358,8 +426,9 @@ test "getting followers", %{conn: conn} do
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
{:ok, user} = User.follow(user, other_user)
|
{:ok, user} = User.follow(user, other_user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> get("/api/v1/accounts/#{other_user.id}/followers")
|
conn
|
||||||
|
|> get("/api/v1/accounts/#{other_user.id}/followers")
|
||||||
|
|
||||||
assert [%{"id" => id}] = json_response(conn, 200)
|
assert [%{"id" => id}] = json_response(conn, 200)
|
||||||
assert id == to_string(user.id)
|
assert id == to_string(user.id)
|
||||||
|
@ -370,8 +439,9 @@ test "getting following", %{conn: conn} do
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
{:ok, user} = User.follow(user, other_user)
|
{:ok, user} = User.follow(user, other_user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> get("/api/v1/accounts/#{user.id}/following")
|
conn
|
||||||
|
|> get("/api/v1/accounts/#{user.id}/following")
|
||||||
|
|
||||||
assert [%{"id" => id}] = json_response(conn, 200)
|
assert [%{"id" => id}] = json_response(conn, 200)
|
||||||
assert id == to_string(other_user.id)
|
assert id == to_string(other_user.id)
|
||||||
|
@ -381,23 +451,28 @@ test "following / unfollowing a user", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> post("/api/v1/accounts/#{other_user.id}/follow")
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/accounts/#{other_user.id}/follow")
|
||||||
|
|
||||||
assert %{"id" => _id, "following" => true} = json_response(conn, 200)
|
assert %{"id" => _id, "following" => true} = json_response(conn, 200)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = Repo.get(User, user.id)
|
||||||
conn = build_conn()
|
|
||||||
|> assign(:user, user)
|
conn =
|
||||||
|> post("/api/v1/accounts/#{other_user.id}/unfollow")
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/accounts/#{other_user.id}/unfollow")
|
||||||
|
|
||||||
assert %{"id" => _id, "following" => false} = json_response(conn, 200)
|
assert %{"id" => _id, "following" => false} = json_response(conn, 200)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = Repo.get(User, user.id)
|
||||||
conn = build_conn()
|
|
||||||
|> assign(:user, user)
|
conn =
|
||||||
|> post("/api/v1/follows", %{"uri" => other_user.nickname})
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/follows", %{"uri" => other_user.nickname})
|
||||||
|
|
||||||
assert %{"id" => id} = json_response(conn, 200)
|
assert %{"id" => id} = json_response(conn, 200)
|
||||||
assert id == to_string(other_user.id)
|
assert id == to_string(other_user.id)
|
||||||
|
@ -407,16 +482,19 @@ test "blocking / unblocking a user", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> post("/api/v1/accounts/#{other_user.id}/block")
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/accounts/#{other_user.id}/block")
|
||||||
|
|
||||||
assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
|
assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = Repo.get(User, user.id)
|
||||||
conn = build_conn()
|
|
||||||
|> assign(:user, user)
|
conn =
|
||||||
|> post("/api/v1/accounts/#{other_user.id}/unblock")
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/accounts/#{other_user.id}/unblock")
|
||||||
|
|
||||||
assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
|
assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
|
||||||
end
|
end
|
||||||
|
@ -427,9 +505,10 @@ test "getting a list of blocks", %{conn: conn} do
|
||||||
|
|
||||||
{:ok, user} = User.block(user, other_user)
|
{:ok, user} = User.block(user, other_user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> get("/api/v1/blocks")
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/blocks")
|
||||||
|
|
||||||
other_user_id = to_string(other_user.id)
|
other_user_id = to_string(other_user.id)
|
||||||
assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
|
assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
|
||||||
|
@ -440,10 +519,11 @@ test "unimplemented mute endpoints" do
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
||||||
["mute", "unmute"]
|
["mute", "unmute"]
|
||||||
|> Enum.each(fn(endpoint) ->
|
|> Enum.each(fn endpoint ->
|
||||||
conn = build_conn()
|
conn =
|
||||||
|> assign(:user, user)
|
build_conn()
|
||||||
|> post("/api/v1/accounts/#{other_user.id}/#{endpoint}")
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/accounts/#{other_user.id}/#{endpoint}")
|
||||||
|
|
||||||
assert %{"id" => id} = json_response(conn, 200)
|
assert %{"id" => id} = json_response(conn, 200)
|
||||||
assert id == to_string(other_user.id)
|
assert id == to_string(other_user.id)
|
||||||
|
@ -454,10 +534,11 @@ test "unimplemented mutes, follow_requests, blocks, domain blocks" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
["blocks", "domain_blocks", "mutes", "follow_requests"]
|
["blocks", "domain_blocks", "mutes", "follow_requests"]
|
||||||
|> Enum.each(fn(endpoint) ->
|
|> Enum.each(fn endpoint ->
|
||||||
conn = build_conn()
|
conn =
|
||||||
|> assign(:user, user)
|
build_conn()
|
||||||
|> get("/api/v1/#{endpoint}")
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/#{endpoint}")
|
||||||
|
|
||||||
assert [] = json_response(conn, 200)
|
assert [] = json_response(conn, 200)
|
||||||
end)
|
end)
|
||||||
|
@ -468,9 +549,10 @@ test "account search", %{conn: conn} do
|
||||||
_user_two = insert(:user, %{nickname: "shp@shitposter.club"})
|
_user_two = insert(:user, %{nickname: "shp@shitposter.club"})
|
||||||
user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
|
user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> get("/api/v1/accounts/search", %{"q" => "2hu"})
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/accounts/search", %{"q" => "2hu"})
|
||||||
|
|
||||||
assert [account] = json_response(conn, 200)
|
assert [account] = json_response(conn, 200)
|
||||||
assert account["id"] == to_string(user_three.id)
|
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, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"})
|
||||||
{:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
|
{:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> get("/api/v1/search", %{"q" => "2hu"})
|
conn
|
||||||
|
|> get("/api/v1/search", %{"q" => "2hu"})
|
||||||
|
|
||||||
assert results = json_response(conn, 200)
|
assert results = json_response(conn, 200)
|
||||||
|
|
||||||
|
@ -499,19 +582,22 @@ test "search", %{conn: conn} do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "search fetches remote statuses", %{conn: conn} do
|
test "search fetches remote statuses", %{conn: conn} do
|
||||||
capture_log fn ->
|
capture_log(fn ->
|
||||||
conn = conn
|
conn =
|
||||||
|> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
|
conn
|
||||||
|
|> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
|
||||||
|
|
||||||
assert results = json_response(conn, 200)
|
assert results = json_response(conn, 200)
|
||||||
|
|
||||||
[status] = results["statuses"]
|
[status] = results["statuses"]
|
||||||
assert status["uri"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
|
assert status["uri"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
|
||||||
end
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "search fetches remote accounts", %{conn: conn} do
|
test "search fetches remote accounts", %{conn: conn} do
|
||||||
conn = conn
|
conn =
|
||||||
|> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"})
|
conn
|
||||||
|
|> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"})
|
||||||
|
|
||||||
assert results = json_response(conn, 200)
|
assert results = json_response(conn, 200)
|
||||||
[account] = results["accounts"]
|
[account] = results["accounts"]
|
||||||
|
@ -527,9 +613,10 @@ test "returns the favorites of a user", %{conn: conn} do
|
||||||
|
|
||||||
{:ok, _, _} = CommonAPI.favorite(activity.id, user)
|
{:ok, _, _} = CommonAPI.favorite(activity.id, user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> get("/api/v1/favourites")
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/favourites")
|
||||||
|
|
||||||
assert [status] = json_response(conn, 200)
|
assert [status] = json_response(conn, 200)
|
||||||
assert status["id"] == to_string(activity.id)
|
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
|
test "updates the user's bio", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> patch("/api/v1/accounts/update_credentials", %{"note" => "I drink #cofe"})
|
|> assign(:user, user)
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{"note" => "I drink #cofe"})
|
||||||
|
|
||||||
assert user = json_response(conn, 200)
|
assert user = json_response(conn, 200)
|
||||||
assert user["note"] == "I drink #cofe"
|
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
|
test "updates the user's name", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> patch("/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
|
|> assign(:user, user)
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
|
||||||
|
|
||||||
assert user = json_response(conn, 200)
|
assert user = json_response(conn, 200)
|
||||||
assert user["display_name"] == "markorepairs"
|
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
|
test "updates the user's avatar", %{conn: conn} do
|
||||||
user = insert(:user)
|
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
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
|
|> assign(:user, user)
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
|
||||||
|
|
||||||
assert user = json_response(conn, 200)
|
assert user = json_response(conn, 200)
|
||||||
assert user["avatar"] != "https://placehold.it/48x48"
|
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
|
test "updates the user's banner", %{conn: conn} do
|
||||||
user = insert(:user)
|
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
|
conn =
|
||||||
|> assign(:user, user)
|
conn
|
||||||
|> patch("/api/v1/accounts/update_credentials", %{"header" => new_header})
|
|> assign(:user, user)
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{"header" => new_header})
|
||||||
|
|
||||||
assert user = json_response(conn, 200)
|
assert user = json_response(conn, 200)
|
||||||
assert user["header"] != "https://placehold.it/700x335"
|
assert user["header"] != "https://placehold.it/700x335"
|
||||||
|
@ -594,8 +693,9 @@ test "get instance information", %{conn: conn} do
|
||||||
|
|
||||||
Pleroma.Stats.update_stats()
|
Pleroma.Stats.update_stats()
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> get("/api/v1/instance")
|
conn
|
||||||
|
|> get("/api/v1/instance")
|
||||||
|
|
||||||
assert result = json_response(conn, 200)
|
assert result = json_response(conn, 200)
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,9 @@ test "a note activity" do
|
||||||
|
|
||||||
status = StatusView.render("status.json", %{activity: note})
|
status = StatusView.render("status.json", %{activity: note})
|
||||||
|
|
||||||
created_at = (note.data["object"]["published"] || "")
|
created_at =
|
||||||
|> String.replace(~r/\.\d+Z/, ".000Z")
|
(note.data["object"]["published"] || "")
|
||||||
|
|> String.replace(~r/\.\d+Z/, ".000Z")
|
||||||
|
|
||||||
expected = %{
|
expected = %{
|
||||||
id: to_string(note.id),
|
id: to_string(note.id),
|
||||||
|
@ -57,7 +58,9 @@ test "a note activity" do
|
||||||
test "a reply" do
|
test "a reply" do
|
||||||
note = insert(:note_activity)
|
note = insert(:note_activity)
|
||||||
user = insert(:user)
|
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})
|
status = StatusView.render("status.json", %{activity: activity})
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,15 @@ defmodule Pleroma.Web.OAuth.AuthorizationTest do
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
test "create an authorization token for a valid app" do
|
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)
|
user = insert(:user)
|
||||||
|
|
||||||
{:ok, auth} = Authorization.create_authorization(app, user)
|
{:ok, auth} = Authorization.create_authorization(app, user)
|
||||||
|
@ -16,7 +24,15 @@ test "create an authorization token for a valid app" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "use up a token" do
|
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)
|
user = insert(:user)
|
||||||
|
|
||||||
{:ok, auth} = Authorization.create_authorization(app, user)
|
{:ok, auth} = Authorization.create_authorization(app, user)
|
||||||
|
@ -30,7 +46,7 @@ test "use up a token" do
|
||||||
expired_auth = %Authorization{
|
expired_auth = %Authorization{
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
app_id: app.id,
|
app_id: app.id,
|
||||||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now, -10),
|
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), -10),
|
||||||
token: "mytoken",
|
token: "mytoken",
|
||||||
used: false
|
used: false
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,15 @@ defmodule Pleroma.Web.OAuth.TokenTest do
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
test "exchanges a auth token for an access token" do
|
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)
|
user = insert(:user)
|
||||||
|
|
||||||
{:ok, auth} = Authorization.create_authorization(app, 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)
|
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
|
end
|
||||||
|
|
||||||
test "a note activity" do
|
test "a note activity" do
|
||||||
|
@ -46,7 +49,7 @@ test "a note activity" do
|
||||||
|
|
||||||
tuple = ActivityRepresenter.to_simple_form(note_activity, user)
|
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)
|
assert clean(res) == clean(expected)
|
||||||
end
|
end
|
||||||
|
@ -61,7 +64,10 @@ test "a reply note" do
|
||||||
answer = %{answer | data: data}
|
answer = %{answer | data: data}
|
||||||
|
|
||||||
note_object = Object.get_by_ap_id(note.data["object"]["id"])
|
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"])
|
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)
|
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)
|
assert clean(res) == clean(expected)
|
||||||
end
|
end
|
||||||
|
@ -102,9 +108,11 @@ test "an announce activity" do
|
||||||
|
|
||||||
note_user = User.get_cached_by_ap_id(note.data["actor"])
|
note_user = User.get_cached_by_ap_id(note.data["actor"])
|
||||||
note = Repo.get(Activity, note.id)
|
note = Repo.get(Activity, note.id)
|
||||||
note_xml = ActivityRepresenter.to_simple_form(note, note_user, true)
|
|
||||||
|> :xmerl.export_simple_content(:xmerl_xml)
|
note_xml =
|
||||||
|> to_string
|
ActivityRepresenter.to_simple_form(note, note_user, true)
|
||||||
|
|> :xmerl.export_simple_content(:xmerl_xml)
|
||||||
|
|> to_string
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
<activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
|
<activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
|
||||||
|
@ -120,13 +128,16 @@ test "an announce activity" do
|
||||||
<activity:object>
|
<activity:object>
|
||||||
#{note_xml}
|
#{note_xml}
|
||||||
</activity:object>
|
</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"/>
|
<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)
|
announce_xml =
|
||||||
|> :xmerl.export_simple_content(:xmerl_xml)
|
ActivityRepresenter.to_simple_form(announce, user)
|
||||||
|> to_string
|
|> :xmerl.export_simple_content(:xmerl_xml)
|
||||||
|
|> to_string
|
||||||
|
|
||||||
assert clean(expected) == clean(announce_xml)
|
assert clean(expected) == clean(announce_xml)
|
||||||
end
|
end
|
||||||
|
@ -139,7 +150,7 @@ test "a like activity" do
|
||||||
tuple = ActivityRepresenter.to_simple_form(like, user)
|
tuple = ActivityRepresenter.to_simple_form(like, user)
|
||||||
refute is_nil(tuple)
|
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 = """
|
expected = """
|
||||||
<activity:verb>http://activitystrea.ms/schema/1.0/favorite</activity:verb>
|
<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 ref="#{like.data["context"]}" rel="ostatus:conversation" />
|
||||||
<link rel="self" type="application/atom+xml" href="#{like.data["id"]}"/>
|
<link rel="self" type="application/atom+xml" href="#{like.data["id"]}"/>
|
||||||
<thr:in-reply-to ref="#{note.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"/>
|
<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
|
test "a follow activity" do
|
||||||
follower = insert(:user)
|
follower = insert(:user)
|
||||||
followed = insert(:user)
|
followed = insert(:user)
|
||||||
{:ok, activity} = ActivityPub.insert(%{
|
|
||||||
"type" => "Follow",
|
{:ok, activity} =
|
||||||
"actor" => follower.ap_id,
|
ActivityPub.insert(%{
|
||||||
"object" => followed.ap_id,
|
"type" => "Follow",
|
||||||
"to" => [followed.ap_id]
|
"actor" => follower.ap_id,
|
||||||
})
|
"object" => followed.ap_id,
|
||||||
|
"to" => [followed.ap_id]
|
||||||
|
})
|
||||||
|
|
||||||
tuple = ActivityRepresenter.to_simple_form(activity, follower)
|
tuple = ActivityRepresenter.to_simple_form(activity, follower)
|
||||||
|
|
||||||
refute is_nil(tuple)
|
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 = """
|
expected = """
|
||||||
<activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
|
<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>
|
<uri>#{activity.data["object"]}</uri>
|
||||||
</activity:object>
|
</activity:object>
|
||||||
<link rel="self" type="application/atom+xml" href="#{activity.data["id"]}"/>
|
<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)
|
assert clean(res) == clean(expected)
|
||||||
|
@ -209,7 +226,7 @@ test "an unfollow activity" do
|
||||||
|
|
||||||
refute is_nil(tuple)
|
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 = """
|
expected = """
|
||||||
<activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
|
<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>
|
<uri>#{followed.ap_id}</uri>
|
||||||
</activity:object>
|
</activity:object>
|
||||||
<link rel="self" type="application/atom+xml" href="#{activity.data["id"]}"/>
|
<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)
|
assert clean(res) == clean(expected)
|
||||||
|
@ -233,13 +252,22 @@ test "an unfollow activity" do
|
||||||
|
|
||||||
test "a delete" do
|
test "a delete" do
|
||||||
user = insert(:user)
|
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)
|
tuple = ActivityRepresenter.to_simple_form(activity, nil)
|
||||||
|
|
||||||
refute is_nil(tuple)
|
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 = """
|
expected = """
|
||||||
<activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
|
<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])
|
tuple = FeedRepresenter.to_simple_form(user, [note_activity], [user])
|
||||||
|
|
||||||
most_recent_update = note_activity.updated_at
|
most_recent_update =
|
||||||
|> NaiveDateTime.to_iso8601
|
note_activity.updated_at
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> to_string
|
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)
|
user_xml =
|
||||||
|> :xmerl.export_simple_content(:xmerl_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 = """
|
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">
|
<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>
|
</entry>
|
||||||
</feed>
|
</feed>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert clean(res) == clean(expected)
|
assert clean(res) == clean(expected)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,13 @@ test "it removes the mentioned activity" do
|
||||||
|
|
||||||
{:ok, like, _object} = Pleroma.Web.ActivityPub.ActivityPub.like(user, object)
|
{:ok, like, _object} = Pleroma.Web.ActivityPub.ActivityPub.like(user, object)
|
||||||
|
|
||||||
incoming = File.read!("test/fixtures/delete.xml")
|
incoming =
|
||||||
|> String.replace("tag:mastodon.sdf.org,2017-06-10:objectId=310513:objectType=Status", note.data["object"]["id"])
|
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)
|
{:ok, [delete]} = OStatus.handle_incoming(incoming)
|
||||||
|
|
||||||
refute Repo.get(Activity, note.id)
|
refute Repo.get(Activity, note.id)
|
||||||
|
|
|
@ -7,9 +7,11 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
|
||||||
test "decodes a salmon", %{conn: conn} do
|
test "decodes a salmon", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
salmon = File.read!("test/fixtures/salmon.xml")
|
salmon = File.read!("test/fixtures/salmon.xml")
|
||||||
conn = conn
|
|
||||||
|> put_req_header("content-type", "application/atom+xml")
|
conn =
|
||||||
|> post("/users/#{user.nickname}/salmon", salmon)
|
conn
|
||||||
|
|> put_req_header("content-type", "application/atom+xml")
|
||||||
|
|> post("/users/#{user.nickname}/salmon", salmon)
|
||||||
|
|
||||||
assert response(conn, 200)
|
assert response(conn, 200)
|
||||||
end
|
end
|
||||||
|
@ -17,21 +19,30 @@ test "decodes a salmon", %{conn: conn} do
|
||||||
test "decodes a salmon with a changed magic key", %{conn: conn} do
|
test "decodes a salmon with a changed magic key", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
salmon = File.read!("test/fixtures/salmon.xml")
|
salmon = File.read!("test/fixtures/salmon.xml")
|
||||||
conn = conn
|
|
||||||
|> put_req_header("content-type", "application/atom+xml")
|
conn =
|
||||||
|> post("/users/#{user.nickname}/salmon", salmon)
|
conn
|
||||||
|
|> put_req_header("content-type", "application/atom+xml")
|
||||||
|
|> post("/users/#{user.nickname}/salmon", salmon)
|
||||||
|
|
||||||
assert response(conn, 200)
|
assert response(conn, 200)
|
||||||
|
|
||||||
# Set a wrong magic-key for a user so it has to refetch
|
# 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")
|
salmon_user = User.get_by_ap_id("http://gs.example.org:4040/index.php/user/1")
|
||||||
info = salmon_user.info
|
# Wrong key
|
||||||
|> Map.put("magic_key", "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwrong1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB") # 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}))
|
Repo.update(User.info_changeset(salmon_user, %{info: info}))
|
||||||
|
|
||||||
conn = build_conn()
|
conn =
|
||||||
|> put_req_header("content-type", "application/atom+xml")
|
build_conn()
|
||||||
|> post("/users/#{user.nickname}/salmon", salmon)
|
|> put_req_header("content-type", "application/atom+xml")
|
||||||
|
|> post("/users/#{user.nickname}/salmon", salmon)
|
||||||
|
|
||||||
assert response(conn, 200)
|
assert response(conn, 200)
|
||||||
end
|
end
|
||||||
|
@ -40,8 +51,9 @@ test "gets a feed", %{conn: conn} do
|
||||||
note_activity = insert(:note_activity)
|
note_activity = insert(:note_activity)
|
||||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> get("/users/#{user.nickname}/feed.atom")
|
conn
|
||||||
|
|> get("/users/#{user.nickname}/feed.atom")
|
||||||
|
|
||||||
assert response(conn, 200) =~ note_activity.data["object"]["content"]
|
assert response(conn, 200) =~ note_activity.data["object"]["content"]
|
||||||
end
|
end
|
||||||
|
@ -49,27 +61,30 @@ test "gets a feed", %{conn: conn} do
|
||||||
test "gets an object", %{conn: conn} do
|
test "gets an object", %{conn: conn} do
|
||||||
note_activity = insert(:note_activity)
|
note_activity = insert(:note_activity)
|
||||||
user = User.get_by_ap_id(note_activity.data["actor"])
|
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}"
|
url = "/objects/#{uuid}"
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> get(url)
|
conn
|
||||||
|
|> get(url)
|
||||||
|
|
||||||
expected = ActivityRepresenter.to_simple_form(note_activity, user, true)
|
expected =
|
||||||
|> ActivityRepresenter.wrap_with_entry
|
ActivityRepresenter.to_simple_form(note_activity, user, true)
|
||||||
|> :xmerl.export_simple(:xmerl_xml)
|
|> ActivityRepresenter.wrap_with_entry()
|
||||||
|> to_string
|
|> :xmerl.export_simple(:xmerl_xml)
|
||||||
|
|> to_string
|
||||||
|
|
||||||
assert response(conn, 200) == expected
|
assert response(conn, 200) == expected
|
||||||
end
|
end
|
||||||
|
|
||||||
test "gets an activity", %{conn: conn} do
|
test "gets an activity", %{conn: conn} do
|
||||||
note_activity = insert(:note_activity)
|
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}"
|
url = "/activities/#{uuid}"
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> get(url)
|
conn
|
||||||
|
|> get(url)
|
||||||
|
|
||||||
assert response(conn, 200)
|
assert response(conn, 200)
|
||||||
end
|
end
|
||||||
|
@ -78,8 +93,9 @@ test "gets a notice", %{conn: conn} do
|
||||||
note_activity = insert(:note_activity)
|
note_activity = insert(:note_activity)
|
||||||
url = "/notice/#{note_activity.id}"
|
url = "/notice/#{note_activity.id}"
|
||||||
|
|
||||||
conn = conn
|
conn =
|
||||||
|> get(url)
|
conn
|
||||||
|
|> get(url)
|
||||||
|
|
||||||
assert response(conn, 200)
|
assert response(conn, 200)
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,12 +20,18 @@ test "handle incoming note - GS, Salmon" do
|
||||||
assert user.info["note_count"] == 1
|
assert user.info["note_count"] == 1
|
||||||
assert activity.data["type"] == "Create"
|
assert activity.data["type"] == "Create"
|
||||||
assert activity.data["object"]["type"] == "Note"
|
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["published"] == "2017-04-23T14:51:03+00:00"
|
||||||
assert activity.data["object"]["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 "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
|
assert activity.local == false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -65,10 +71,12 @@ test "handle incoming notes with tags" do
|
||||||
test "handle incoming notes - Mastodon, salmon, reply" do
|
test "handle incoming notes - Mastodon, salmon, reply" do
|
||||||
# It uses the context of the replied to object
|
# It uses the context of the replied to object
|
||||||
Repo.insert!(%Object{
|
Repo.insert!(%Object{
|
||||||
data: %{
|
data: %{
|
||||||
"id" => "https://pleroma.soykaf.com/objects/c237d966-ac75-4fe3-a87a-d89d71a3a7a4",
|
"id" => "https://pleroma.soykaf.com/objects/c237d966-ac75-4fe3-a87a-d89d71a3a7a4",
|
||||||
"context" => "2hu"
|
"context" => "2hu"
|
||||||
}})
|
}
|
||||||
|
})
|
||||||
|
|
||||||
incoming = File.read!("test/fixtures/incoming_reply_mastodon.xml")
|
incoming = File.read!("test/fixtures/incoming_reply_mastodon.xml")
|
||||||
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
{: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["type"] == "Create"
|
||||||
assert activity.data["object"]["type"] == "Note"
|
assert activity.data["object"]["type"] == "Note"
|
||||||
assert activity.data["object"]["actor"] == "https://social.heldscal.la/user/23211"
|
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"]
|
assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -141,9 +154,11 @@ test "handle incoming retweets - GS, subscription - local message" do
|
||||||
incoming = File.read!("test/fixtures/share-gs-local.xml")
|
incoming = File.read!("test/fixtures/share-gs-local.xml")
|
||||||
note_activity = insert(:note_activity)
|
note_activity = insert(:note_activity)
|
||||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||||
incoming = incoming
|
|
||||||
|> String.replace("LOCAL_ID", note_activity.data["object"]["id"])
|
incoming =
|
||||||
|> String.replace("LOCAL_USER", user.ap_id)
|
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)
|
{: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["type"] == "Announce"
|
||||||
assert activity.data["actor"] == "https://mastodon.social/users/lambadalambda"
|
assert activity.data["actor"] == "https://mastodon.social/users/lambadalambda"
|
||||||
assert activity.data["object"] == retweeted_activity.data["object"]["id"]
|
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
|
refute activity.local
|
||||||
assert retweeted_activity.data["type"] == "Create"
|
assert retweeted_activity.data["type"] == "Create"
|
||||||
|
@ -178,35 +195,42 @@ test "handle incoming retweets - Mastodon, salmon" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "handle incoming favorites - GS, websub" do
|
test "handle incoming favorites - GS, websub" do
|
||||||
capture_log fn ->
|
capture_log(fn ->
|
||||||
incoming = File.read!("test/fixtures/favorite.xml")
|
incoming = File.read!("test/fixtures/favorite.xml")
|
||||||
{:ok, [[activity, favorited_activity]]} = OStatus.handle_incoming(incoming)
|
{:ok, [[activity, favorited_activity]]} = OStatus.handle_incoming(incoming)
|
||||||
|
|
||||||
assert activity.data["type"] == "Like"
|
assert activity.data["type"] == "Like"
|
||||||
assert activity.data["actor"] == "https://social.heldscal.la/user/23211"
|
assert activity.data["actor"] == "https://social.heldscal.la/user/23211"
|
||||||
assert activity.data["object"] == favorited_activity.data["object"]["id"]
|
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
|
refute activity.local
|
||||||
assert favorited_activity.data["type"] == "Create"
|
assert favorited_activity.data["type"] == "Create"
|
||||||
assert favorited_activity.data["actor"] == "https://shitposter.club/user/1"
|
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
|
refute favorited_activity.local
|
||||||
end
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "handle conversation references" do
|
test "handle conversation references" do
|
||||||
incoming = File.read!("test/fixtures/mastodon_conversation.xml")
|
incoming = File.read!("test/fixtures/mastodon_conversation.xml")
|
||||||
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
{: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
|
end
|
||||||
|
|
||||||
test "handle incoming favorites with locally available object - GS, websub" do
|
test "handle incoming favorites with locally available object - GS, websub" do
|
||||||
note_activity = insert(:note_activity)
|
note_activity = insert(:note_activity)
|
||||||
|
|
||||||
incoming = File.read!("test/fixtures/favorite_with_local_note.xml")
|
incoming =
|
||||||
|> String.replace("localid", note_activity.data["object"]["id"])
|
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)
|
{: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["type"] == "Create"
|
||||||
assert activity.data["object"]["type"] == "Note"
|
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 "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"]
|
assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -234,7 +264,10 @@ test "handle incoming follows" do
|
||||||
incoming = File.read!("test/fixtures/follow.xml")
|
incoming = File.read!("test/fixtures/follow.xml")
|
||||||
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
||||||
assert activity.data["type"] == "Follow"
|
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["actor"] == "https://social.heldscal.la/user/23211"
|
||||||
assert activity.data["object"] == "https://pawoo.net/users/pekorino"
|
assert activity.data["object"] == "https://pawoo.net/users/pekorino"
|
||||||
refute activity.local
|
refute activity.local
|
||||||
|
@ -304,7 +337,8 @@ test "it returns user info in a hash" do
|
||||||
|
|
||||||
expected = %{
|
expected = %{
|
||||||
"hub" => "https://social.heldscal.la/main/push/hub",
|
"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",
|
"name" => "shp",
|
||||||
"nickname" => "shp",
|
"nickname" => "shp",
|
||||||
"salmon" => "https://social.heldscal.la/main/salmon/user/29191",
|
"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",
|
"host" => "social.heldscal.la",
|
||||||
"fqn" => user,
|
"fqn" => user,
|
||||||
"bio" => "cofe",
|
"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}",
|
"subscribe_address" => "https://social.heldscal.la/main/ostatussub?profile={uri}",
|
||||||
"ap_id" => nil
|
"ap_id" => nil
|
||||||
}
|
}
|
||||||
|
|
||||||
assert data == expected
|
assert data == expected
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -329,7 +373,8 @@ test "it works with the uri" do
|
||||||
|
|
||||||
expected = %{
|
expected = %{
|
||||||
"hub" => "https://social.heldscal.la/main/push/hub",
|
"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",
|
"name" => "shp",
|
||||||
"nickname" => "shp",
|
"nickname" => "shp",
|
||||||
"salmon" => "https://social.heldscal.la/main/salmon/user/29191",
|
"salmon" => "https://social.heldscal.la/main/salmon/user/29191",
|
||||||
|
@ -339,28 +384,40 @@ test "it works with the uri" do
|
||||||
"host" => "social.heldscal.la",
|
"host" => "social.heldscal.la",
|
||||||
"fqn" => user,
|
"fqn" => user,
|
||||||
"bio" => "cofe",
|
"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}",
|
"subscribe_address" => "https://social.heldscal.la/main/ostatussub?profile={uri}",
|
||||||
"ap_id" => nil
|
"ap_id" => nil
|
||||||
}
|
}
|
||||||
|
|
||||||
assert data == expected
|
assert data == expected
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "fetching a status by it's HTML url" do
|
describe "fetching a status by it's HTML url" do
|
||||||
test "it builds a missing status from an 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"
|
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["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
|
end
|
||||||
|
|
||||||
test "it works for atom notes, too" do
|
test "it works for atom notes, too" do
|
||||||
url = "https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056"
|
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["actor"] == "https://social.sakamoto.gq/users/eal"
|
||||||
assert activity.data["object"]["id"] == url
|
assert activity.data["object"]["id"] == url
|
||||||
end
|
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")
|
incoming = File.read!("test/fixtures/nil_mention_entry.xml")
|
||||||
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
{: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
|
||||||
end
|
end
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue