Format the code.

This commit is contained in:
lain 2018-03-30 15:01:53 +02:00
parent 480932c8e5
commit 4afbef39f4
111 changed files with 4912 additions and 2769 deletions

View File

@ -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 @@ config :pleroma, Pleroma.Web.Endpoint,
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, :websub, Pleroma.Web.Websub
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, :instance,
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"

View File

@ -7,7 +7,10 @@ use Mix.Config
# 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 @@ config :pleroma, Pleroma.Repo,
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

View File

@ -9,8 +9,7 @@ config :pleroma, Pleroma.Web.Endpoint,
# 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 @@ config :pleroma, Pleroma.Repo,
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

View File

@ -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
)

View File

@ -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)

View File

@ -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

View File

@ -9,11 +9,20 @@ defmodule Mix.Tasks.GeneratePasswordReset 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

View File

@ -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

View File

@ -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,

View File

@ -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 @@ defmodule Pleroma.Activity 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

View File

@ -7,23 +7,34 @@ defmodule Pleroma.Application 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

View File

@ -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 @@ defmodule Pleroma.Formatter 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 @@ defmodule Pleroma.Formatter 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 @@ defmodule Pleroma.Formatter 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

View File

@ -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

View File

@ -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 @@ defmodule Pleroma.Notification do
end end
end end
end end

View File

@ -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 @@ defmodule Pleroma.Object 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

View File

@ -14,8 +14,7 @@ defmodule Pleroma.Plugs.AuthenticationPlug 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 @@ defmodule Pleroma.Plugs.AuthenticationPlug 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 @@ defmodule Pleroma.Plugs.AuthenticationPlug 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

View File

@ -9,11 +9,14 @@ defmodule Pleroma.Plugs.OAuthPlug 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),

View File

@ -18,22 +18,31 @@ defmodule Pleroma.Stats 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)

View File

@ -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 @@ defmodule Pleroma.Upload 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 @@ defmodule Pleroma.Upload 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 @@ defmodule Pleroma.Upload 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

View File

@ -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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User do
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

View File

@ -18,7 +18,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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

View File

@ -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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController 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

View File

@ -25,21 +25,25 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier 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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier 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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier 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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier 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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier 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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier 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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier 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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier 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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier 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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier 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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier 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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier 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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier 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

View File

@ -26,7 +26,7 @@ defmodule Pleroma.Web.ActivityPub.Utils 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 @@ defmodule Pleroma.Web.ActivityPub.Utils 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 @@ defmodule Pleroma.Web.ActivityPub.Utils do
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 @@ defmodule Pleroma.Web.ActivityPub.Utils 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 @@ defmodule Pleroma.Web.ActivityPub.Utils 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 @@ defmodule Pleroma.Web.ActivityPub.Utils 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 @@ defmodule Pleroma.Web.ActivityPub.Utils do
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 @@ defmodule Pleroma.Web.ActivityPub.Utils 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 @@ defmodule Pleroma.Web.ActivityPub.Utils do
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 @@ defmodule Pleroma.Web.ActivityPub.Utils do
@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 @@ defmodule Pleroma.Web.ActivityPub.Utils do
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 @@ defmodule Pleroma.Web.ActivityPub.Utils 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,

View File

@ -12,6 +12,7 @@ defmodule Pleroma.Web.ActivityPub.UserView 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 @@ defmodule Pleroma.Web.ActivityPub.UserView 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 @@ defmodule Pleroma.Web.ActivityPub.UserView 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 @@ defmodule Pleroma.Web.ActivityPub.UserView 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 @@ defmodule Pleroma.Web.ActivityPub.UserView 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 @@ defmodule Pleroma.Web.ActivityPub.UserView 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

View File

@ -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

View File

@ -9,19 +9,21 @@ defmodule Pleroma.Web.ChatChannel 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 @@ defmodule Pleroma.Web.ChatChannel.ChatChannelState 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

View File

@ -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 @@ defmodule Pleroma.Web.CommonAPI 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 @@ defmodule Pleroma.Web.CommonAPI 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

View File

@ -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 @@ defmodule Pleroma.Web.CommonAPI.Utils 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 @@ defmodule Pleroma.Web.CommonAPI.Utils 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 @@ defmodule Pleroma.Web.CommonAPI.Utils 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 @@ defmodule Pleroma.Web.CommonAPI.Utils do
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 @@ defmodule Pleroma.Web.CommonAPI.Utils 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 -> ""

View File

@ -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

View File

@ -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 @@ defmodule Pleroma.Web.Federator 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 @@ defmodule Pleroma.Web.Federator 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 @@ defmodule Pleroma.Web.Federator 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 @@ defmodule Pleroma.Web.Federator do
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 @@ defmodule Pleroma.Web.Federator 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 @@ defmodule Pleroma.Web.Federator 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 @@ defmodule Pleroma.Web.Federator 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 @@ defmodule Pleroma.Web.Federator 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 @@ defmodule Pleroma.Web.Federator 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

View File

@ -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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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

View File

@ -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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonSocket 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

View File

@ -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 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
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 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
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 @@ defmodule Pleroma.Web.MastodonAPI.StatusView 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 @@ defmodule Pleroma.Web.MastodonAPI.StatusView 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),

View File

@ -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 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController 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 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController 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

View File

@ -7,14 +7,15 @@ defmodule Pleroma.Web.MediaProxy do
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 @@ defmodule Pleroma.Web.MediaProxy 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

View File

@ -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

View File

@ -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 @@ defmodule Pleroma.Web.OAuth.Authorization 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

View File

@ -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

View File

@ -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 @@ defmodule Pleroma.Web.OAuth.OAuthController do
# 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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"})
@ -86,6 +112,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do
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

View File

@ -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 @@ defmodule Pleroma.Web.OAuth.Token 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)

View File

@ -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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter 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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
{: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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
{: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

View File

@ -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

View File

@ -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)

View File

@ -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 @@ defmodule Pleroma.Web.OStatus.NoteHandler 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 @@ defmodule Pleroma.Web.OStatus.NoteHandler 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

View File

@ -16,7 +16,7 @@ defmodule Pleroma.Web.OStatus 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 @@ defmodule Pleroma.Web.OStatus 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 @@ defmodule Pleroma.Web.OStatus 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 @@ defmodule Pleroma.Web.OStatus 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 @@ defmodule Pleroma.Web.OStatus 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 @@ defmodule Pleroma.Web.OStatus 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 @@ defmodule Pleroma.Web.OStatus 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 @@ defmodule Pleroma.Web.OStatus 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 @@ defmodule Pleroma.Web.OStatus 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 @@ defmodule Pleroma.Web.OStatus 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 @@ defmodule Pleroma.Web.OStatus 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 @@ defmodule Pleroma.Web.OStatus 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 @@ defmodule Pleroma.Web.OStatus 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 @@ defmodule Pleroma.Web.OStatus 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 ->

View File

@ -16,23 +16,26 @@ defmodule Pleroma.Web.OStatus.OStatusController 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 @@ defmodule Pleroma.Web.OStatus.OStatusController 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 @@ defmodule Pleroma.Web.OStatus.OStatusController 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")

View File

@ -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]},

View File

@ -11,294 +11,305 @@ defmodule Pleroma.Web.Router 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")

View File

@ -38,9 +38,10 @@ defmodule Pleroma.Web.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 @@ defmodule Pleroma.Web.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 @@ defmodule Pleroma.Web.Salmon 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 @@ defmodule Pleroma.Web.Salmon 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 @@ defmodule Pleroma.Web.Salmon 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 @@ defmodule Pleroma.Web.Salmon do
"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)

View File

@ -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 @@ defmodule Pleroma.Web.Streamer 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 @@ defmodule Pleroma.Web.Streamer 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

View File

@ -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 @@ defmodule Pleroma.Web.TwitterAPI.UtilController 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 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|> 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 @@ defmodule Pleroma.Web.TwitterAPI.UtilController 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 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
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 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|> 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 @@ defmodule Pleroma.Web.TwitterAPI.UtilController 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

View File

@ -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 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
} }
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 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
} }
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 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
# 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 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
} }
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 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
"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 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
} }
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 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
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 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
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 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
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

View File

@ -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 @@ defmodule Pleroma.Web.TwitterAPI.Representers.BaseRepresenter 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

View File

@ -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"],

View File

@ -13,37 +13,42 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
[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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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

View File

@ -16,7 +16,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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

View File

@ -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 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
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 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
} }
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 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
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 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
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

View File

@ -14,20 +14,22 @@ defmodule Pleroma.Web.TwitterAPI.UserView 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 @@ defmodule Pleroma.Web.TwitterAPI.UserView 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 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
} }
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

View File

@ -12,6 +12,6 @@ defmodule Pleroma.Web.ErrorView 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

View File

@ -26,8 +26,9 @@ defmodule Pleroma.Web 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 @@ defmodule Pleroma.Web do
end end
def base_url do def base_url do
Pleroma.Web.Endpoint.url Pleroma.Web.Endpoint.url()
end end
end end

View File

@ -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 @@ defmodule Pleroma.Web.WebFinger 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 @@ defmodule Pleroma.Web.WebFinger 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 @@ defmodule Pleroma.Web.WebFinger 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 @@ defmodule Pleroma.Web.WebFinger 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 @@ defmodule Pleroma.Web.WebFinger 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)

View File

@ -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 @@ defmodule Pleroma.Web.WebFinger.WebFingerController 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

View File

@ -26,15 +26,16 @@ defmodule Pleroma.Web.Websub 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 @@ defmodule Pleroma.Web.Websub 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 @@ defmodule Pleroma.Web.Websub do
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 @@ defmodule Pleroma.Web.Websub do
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 @@ defmodule Pleroma.Web.Websub 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 @@ defmodule Pleroma.Web.Websub 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 @@ defmodule Pleroma.Web.Websub 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 @@ defmodule Pleroma.Web.Websub 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 @@ defmodule Pleroma.Web.Websub do
# 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

View File

@ -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

View File

@ -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 @@ defmodule Pleroma.Web.Websub.WebsubController 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

View File

@ -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

View File

@ -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

View File

@ -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 @@ defmodule Phoenix.Transports.WebSocket.Raw 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 @@ defmodule Phoenix.Transports.WebSocket.Raw 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

View File

@ -23,7 +23,7 @@ defmodule Pleroma.XmlBuilder 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 @@ defmodule Pleroma.XmlBuilder 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
View File

@ -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 @@ defmodule Pleroma.Mixfile 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

View File

@ -18,7 +18,9 @@ defmodule Pleroma.ActivityTest 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

View File

@ -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 @@ defmodule Pleroma.FormatterTest 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 @@ defmodule Pleroma.FormatterTest 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 @@ defmodule Pleroma.FormatterTest 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 @@ defmodule Pleroma.FormatterTest 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

View File

@ -10,7 +10,10 @@ defmodule Pleroma.NotificationTest 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 @@ defmodule Pleroma.NotificationTest 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 @@ defmodule Pleroma.NotificationTest 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 @@ defmodule Pleroma.NotificationTest 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 @@ defmodule Pleroma.NotificationTest 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 @@ defmodule Pleroma.NotificationTest 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)

View File

@ -37,22 +37,24 @@ defmodule Pleroma.Plugs.AuthenticationPlugTest 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 @@ defmodule Pleroma.Plugs.AuthenticationPlugTest 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 @@ defmodule Pleroma.Plugs.AuthenticationPlugTest 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 @@ defmodule Pleroma.Plugs.AuthenticationPlugTest 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 @@ defmodule Pleroma.Plugs.AuthenticationPlugTest 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 @@ defmodule Pleroma.Plugs.AuthenticationPlugTest 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 @@ defmodule Pleroma.Plugs.AuthenticationPlugTest 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, %{})

View File

@ -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 @@ defmodule Pleroma.Builders.ActivityBuilder 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)

View File

@ -10,6 +10,7 @@ defmodule Pleroma.Builders.UserBuilder 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

View File

@ -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

View File

@ -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

View File

@ -9,20 +9,27 @@ defmodule Pleroma.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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.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

View File

@ -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

View File

@ -2,4 +2,3 @@ ExUnit.start()
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)

View File

@ -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

View File

@ -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 @@ defmodule Pleroma.UserTest 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 @@ defmodule Pleroma.UserTest 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 @@ defmodule Pleroma.UserTest 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 @@ defmodule Pleroma.UserTest 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 @@ defmodule Pleroma.UserTest 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 @@ defmodule Pleroma.UserTest 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 @@ defmodule Pleroma.UserTest 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 @@ defmodule Pleroma.UserTest 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 @@ defmodule Pleroma.UserTest 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 @@ defmodule Pleroma.UserTest 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

View File

@ -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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest 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)

View File

@ -22,7 +22,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest 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]

View File

@ -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 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest 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 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest 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 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest 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 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest 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 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest 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 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest 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 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest 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 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest 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 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest 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 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest 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)

View File

@ -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 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest 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

View File

@ -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),

View File

@ -14,17 +14,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest 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)

View File

@ -13,8 +13,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest 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 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest 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})

View File

@ -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 @@ defmodule Pleroma.Web.OAuth.AuthorizationTest 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 @@ defmodule Pleroma.Web.OAuth.AuthorizationTest 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
} }

View File

@ -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)

View File

@ -16,9 +16,12 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest 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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest 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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest 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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest 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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest 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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest 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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest 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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest 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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest 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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest 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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest 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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest 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 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest 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>

View File

@ -11,15 +11,19 @@ defmodule Pleroma.Web.OStatus.FeedRepresenterTest 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 @@ defmodule Pleroma.Web.OStatus.FeedRepresenterTest do
</entry> </entry>
</feed> </feed>
""" """
assert clean(res) == clean(expected) assert clean(res) == clean(expected)
end end

View File

@ -14,8 +14,13 @@ defmodule Pleroma.Web.OStatus.DeleteHandlingTest 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)

View File

@ -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 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest 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 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest 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 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest 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 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest 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

View File

@ -20,12 +20,18 @@ defmodule Pleroma.Web.OStatusTest 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 @@ defmodule Pleroma.Web.OStatusTest 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 @@ defmodule Pleroma.Web.OStatusTest 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 @@ defmodule Pleroma.Web.OStatusTest 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 @@ defmodule Pleroma.Web.OStatusTest 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 @@ defmodule Pleroma.Web.OStatusTest 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 @@ defmodule Pleroma.Web.OStatusTest 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 @@ defmodule Pleroma.Web.OStatusTest 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 @@ defmodule Pleroma.Web.OStatusTest 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 @@ defmodule Pleroma.Web.OStatusTest 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 @@ defmodule Pleroma.Web.OStatusTest 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 @@ defmodule Pleroma.Web.OStatusTest 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 @@ defmodule Pleroma.Web.OStatusTest 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