summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/generate_config.ex22
-rw-r--r--lib/mix/tasks/sample_config.eex20
-rw-r--r--lib/mix/tasks/sample_psql.eex8
-rw-r--r--lib/pleroma/PasswordResetToken.ex44
-rw-r--r--lib/pleroma/activity.ex16
-rw-r--r--lib/pleroma/application.ex4
-rw-r--r--lib/pleroma/formatter.ex18
-rw-r--r--lib/pleroma/notification.ex42
-rw-r--r--lib/pleroma/object.ex5
-rw-r--r--lib/pleroma/plugs/authentication_plug.ex3
-rw-r--r--lib/pleroma/plugs/oauth_plug.ex13
-rw-r--r--lib/pleroma/upload.ex39
-rw-r--r--lib/pleroma/user.ex151
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex55
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex19
-rw-r--r--lib/pleroma/web/channels/user_socket.ex12
-rw-r--r--lib/pleroma/web/chat_channel.ex46
-rw-r--r--lib/pleroma/web/common_api/common_api.ex11
-rw-r--r--lib/pleroma/web/common_api/utils.ex22
-rw-r--r--lib/pleroma/web/endpoint.ex3
-rw-r--r--lib/pleroma/web/federator/federator.ex52
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex374
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_socket.ex41
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex10
-rw-r--r--lib/pleroma/web/mastodon_api/views/mastodon_view.ex5
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex23
-rw-r--r--lib/pleroma/web/oauth/oauth_controller.ex14
-rw-r--r--lib/pleroma/web/ostatus/activity_representer.ex24
-rw-r--r--lib/pleroma/web/ostatus/feed_representer.ex2
-rw-r--r--lib/pleroma/web/ostatus/handlers/delete_handler.ex6
-rw-r--r--lib/pleroma/web/ostatus/handlers/note_handler.ex5
-rw-r--r--lib/pleroma/web/ostatus/ostatus.ex28
-rw-r--r--lib/pleroma/web/ostatus/ostatus_controller.ex36
-rw-r--r--lib/pleroma/web/ostatus/user_representer.ex1
-rw-r--r--lib/pleroma/web/router.ex53
-rw-r--r--lib/pleroma/web/salmon/salmon.ex31
-rw-r--r--lib/pleroma/web/streamer.ex112
-rw-r--r--lib/pleroma/web/templates/layout/app.html.eex68
-rw-r--r--lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex19
-rw-r--r--lib/pleroma/web/templates/mastodon_api/mastodon/login.html.eex11
-rw-r--r--lib/pleroma/web/templates/twitter_api/util/invalid_token.html.eex1
-rw-r--r--lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex12
-rw-r--r--lib/pleroma/web/templates/twitter_api/util/password_reset_failed.html.eex1
-rw-r--r--lib/pleroma/web/templates/twitter_api/util/password_reset_success.html.eex1
-rw-r--r--lib/pleroma/web/twitter_api/controllers/util_controller.ex27
-rw-r--r--lib/pleroma/web/twitter_api/representers/activity_representer.ex9
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api.ex30
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api_controller.ex66
-rw-r--r--lib/pleroma/web/twitter_api/views/user_view.ex26
-rw-r--r--lib/pleroma/web/twitter_api/views/util_view.ex4
-rw-r--r--lib/pleroma/web/web_finger/web_finger.ex2
-rw-r--r--lib/pleroma/web/websub/websub.ex6
-rw-r--r--lib/pleroma/web/xml/xml.ex4
-rw-r--r--lib/transports.ex77
-rw-r--r--lib/xml_builder.ex2
55 files changed, 1525 insertions, 211 deletions
diff --git a/lib/mix/tasks/generate_config.ex b/lib/mix/tasks/generate_config.ex
new file mode 100644
index 000000000..f20f93e4d
--- /dev/null
+++ b/lib/mix/tasks/generate_config.ex
@@ -0,0 +1,22 @@
+defmodule Mix.Tasks.GenerateConfig do
+ use Mix.Task
+
+ @shortdoc "Generates a new config"
+ def run(_) do
+ IO.puts("Answer a few questions to generate a new config\n")
+ IO.puts("--- THIS WILL OVERWRITE YOUR config/generated_config.exs! ---\n")
+ domain = IO.gets("What is your domain name? (e.g. pleroma.soykaf.com): ") |> String.trim
+ name = IO.gets("What is the name of your instance? (e.g. Pleroma/Soykaf): ") |> String.trim
+ email = IO.gets("What's your admin email address: ") |> String.trim
+ 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, 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")
+ 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'")
+ File.write("config/setup_db.psql", resultSql)
+ end
+end
diff --git a/lib/mix/tasks/sample_config.eex b/lib/mix/tasks/sample_config.eex
new file mode 100644
index 000000000..85a7c554e
--- /dev/null
+++ b/lib/mix/tasks/sample_config.eex
@@ -0,0 +1,20 @@
+use Mix.Config
+
+config :pleroma, Pleroma.Web.Endpoint,
+ url: [host: "<%= domain %>", scheme: "https", port: 443],
+ secret_key_base: "<%= secret %>"
+
+config :pleroma, :instance,
+ name: "<%= name %>",
+ email: "<%= email %>",
+ limit: 5000,
+ registrations_open: true
+
+# Configure your database
+config :pleroma, Pleroma.Repo,
+ adapter: Ecto.Adapters.Postgres,
+ username: "pleroma",
+ password: "<%= dbpass %>",
+ database: "pleroma_dev",
+ hostname: "localhost",
+ pool_size: 10
diff --git a/lib/mix/tasks/sample_psql.eex b/lib/mix/tasks/sample_psql.eex
new file mode 100644
index 000000000..18e322efc
--- /dev/null
+++ b/lib/mix/tasks/sample_psql.eex
@@ -0,0 +1,8 @@
+CREATE USER pleroma WITH ENCRYPTED PASSWORD '<%= dbpass %>' CREATEDB;
+-- in case someone runs this second time accidentally
+ALTER USER pleroma WITH ENCRYPTED PASSWORD '<%= dbpass %>' CREATEDB;
+CREATE DATABASE pleroma_dev;
+ALTER DATABASE pleroma_dev OWNER TO pleroma;
+\c pleroma_dev;
+--Extensions made by ecto.migrate that need superuser access
+CREATE EXTENSION IF NOT EXISTS citext;
diff --git a/lib/pleroma/PasswordResetToken.ex b/lib/pleroma/PasswordResetToken.ex
new file mode 100644
index 000000000..52b1fcd50
--- /dev/null
+++ b/lib/pleroma/PasswordResetToken.ex
@@ -0,0 +1,44 @@
+defmodule Pleroma.PasswordResetToken do
+ use Ecto.Schema
+
+ import Ecto.Changeset
+
+ alias Pleroma.{User, PasswordResetToken, Repo}
+
+ schema "password_reset_tokens" do
+ belongs_to :user, User
+ field :token, :string
+ field :used, :boolean, default: false
+
+ timestamps()
+ end
+
+ def create_token(%User{} = user) do
+ token = :crypto.strong_rand_bytes(32) |> Base.url_encode64
+
+ token = %PasswordResetToken{
+ user_id: user.id,
+ used: false,
+ token: token
+ }
+
+ Repo.insert(token)
+ end
+
+ def used_changeset(struct) do
+ struct
+ |> cast(%{}, [])
+ |> put_change(:used, true)
+ end
+
+ def reset_password(token, data) do
+ with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
+ %User{} = user <- Repo.get(User, token.user_id),
+ {:ok, _user} <- User.reset_password(user, data),
+ {:ok, token} <- Repo.update(used_changeset(token)) do
+ {:ok, token}
+ else
+ _e -> {:error, token}
+ end
+ end
+end
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 9a5e6fc78..afd09982f 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -6,7 +6,8 @@ defmodule Pleroma.Activity do
schema "activities" do
field :data, :map
field :local, :boolean, default: true
- has_many :notifications, Notification
+ field :actor, :string
+ has_many :notifications, Notification, on_delete: :delete_all
timestamps()
end
@@ -16,24 +17,29 @@ defmodule Pleroma.Activity do
where: fragment("(?)->>'id' = ?", activity.data, ^to_string(ap_id)))
end
+ # TODO:
+ # Go through these and fix them everywhere.
# Wrong name, only returns create activities
def all_by_object_ap_id_q(ap_id) do
from activity in Activity,
- where: fragment("(?)->'object'->>'id' = ?", activity.data, ^to_string(ap_id))
+ where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ?", activity.data, activity.data, ^to_string(ap_id)),
+ where: fragment("(?)->>'type' = 'Create'", activity.data)
end
+ # Wrong name, returns all.
def all_non_create_by_object_ap_id_q(ap_id) do
from activity in Activity,
- where: fragment("(?)->>'object' = ?", activity.data, ^to_string(ap_id))
+ where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ?", activity.data, activity.data, ^to_string(ap_id))
end
+ # Wrong name plz fix thx
def all_by_object_ap_id(ap_id) do
Repo.all(all_by_object_ap_id_q(ap_id))
end
def get_create_activity_by_object_ap_id(ap_id) do
Repo.one(from activity in Activity,
- where: fragment("(?)->'object'->>'id' = ?", activity.data, ^to_string(ap_id))
- and fragment("(?)->>'type' = 'Create'", activity.data))
+ where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ?", activity.data, activity.data, ^to_string(ap_id)),
+ where: fragment("(?)->>'type' = 'Create'", activity.data))
end
end
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 1f0a05568..2969ca3c4 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -19,8 +19,10 @@ defmodule Pleroma.Application do
ttl_interval: 1000,
limit: 2500
]]),
- worker(Pleroma.Web.Federator, [])
+ worker(Pleroma.Web.Federator, []),
+ worker(Pleroma.Web.ChatChannel.ChatChannelState, []),
]
+ ++ if Mix.env == :test, do: [], else: [worker(Pleroma.Web.Streamer, [])]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
# for other strategies and supported options
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index a5eb3b268..c98db2d94 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -1,15 +1,16 @@
defmodule Pleroma.Formatter do
alias Pleroma.User
- @link_regex ~r/https?:\/\/[\w\.\/?=\-#%&]+[\w]/u
+ @link_regex ~r/https?:\/\/[\w\.\/?=\-#%&@~\(\)]+[\w\/]/u
def linkify(text) do
Regex.replace(@link_regex, text, "<a href='\\0'>\\0</a>")
end
@tag_regex ~r/\#\w+/u
- def parse_tags(text) do
+ def parse_tags(text, data \\ %{}) do
Regex.scan(@tag_regex, text)
|> Enum.map(fn (["#" <> tag = full_tag]) -> {full_tag, String.downcase(tag)} end)
+ |> (fn map -> if data["sensitive"], do: [{"#nsfw", "nsfw"}] ++ map, else: map end).()
end
def parse_mentions(text) do
@@ -23,6 +24,15 @@ defmodule Pleroma.Formatter do
|> Enum.filter(fn ({_match, user}) -> user end)
end
+ def html_escape(text) do
+ Regex.split(@link_regex, text, include_captures: true)
+ |> Enum.map_every(2, fn chunk ->
+ {:safe, part} = Phoenix.HTML.html_escape(chunk)
+ part
+ end)
+ |> Enum.join("")
+ end
+
@finmoji [
"a_trusted_friend",
"alandislands",
@@ -122,4 +132,8 @@ defmodule Pleroma.Formatter do
def get_emoji(text) do
Enum.filter(@emoji, fn ({emoji, _}) -> String.contains?(text, ":#{emoji}:") end)
end
+
+ def get_custom_emoji() do
+ @emoji
+ end
end
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 35f817d1d..241d6a9e0 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -36,7 +36,38 @@ defmodule Pleroma.Notification do
Repo.all(query)
end
- def create_notifications(%Activity{id: id, data: %{"to" => to, "type" => type}} = activity) when type in ["Create", "Like", "Announce", "Follow"] do
+ def get(%{id: user_id} = _user, id) do
+ query = from n in Notification,
+ where: n.id == ^id,
+ preload: [:activity]
+
+ notification = Repo.one(query)
+ case notification do
+ %{user_id: ^user_id} ->
+ {:ok, notification}
+ _ ->
+ {:error, "Cannot get notification"}
+ end
+ end
+
+ def clear(user) do
+ query = from n in Notification,
+ where: n.user_id == ^user.id
+
+ Repo.delete_all(query)
+ end
+
+ def dismiss(%{id: user_id} = _user, id) do
+ notification = Repo.get(Notification, id)
+ case notification do
+ %{user_id: ^user_id} ->
+ Repo.delete(notification)
+ _ ->
+ {:error, "Cannot dismiss notification"}
+ end
+ end
+
+ def create_notifications(%Activity{id: _, data: %{"to" => _, "type" => type}} = activity) when type in ["Create", "Like", "Announce", "Follow"] do
users = User.get_notified_from_activity(activity)
notifications = Enum.map(users, fn (user) -> create_notification(activity, user) end)
@@ -46,9 +77,12 @@ defmodule Pleroma.Notification do
# TODO move to sql, too.
def create_notification(%Activity{} = activity, %User{} = user) do
- notification = %Notification{user_id: user.id, activity_id: activity.id}
- {:ok, notification} = Repo.insert(notification)
- notification
+ unless User.blocks?(user, %{ap_id: activity.data["actor"]}) do
+ notification = %Notification{user_id: user.id, activity: activity}
+ {:ok, notification} = Repo.insert(notification)
+ Pleroma.Web.Streamer.stream("user", notification)
+ notification
+ end
end
end
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 5b51d6be3..30ba7b57a 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -15,15 +15,16 @@ defmodule Pleroma.Object do
end
def change(struct, params \\ %{}) do
- changeset = struct
+ struct
|> cast(params, [:data])
|> validate_required([:data])
|> unique_constraint(:ap_id, name: :objects_unique_apid_index)
end
+ def get_by_ap_id(nil), do: nil
def get_by_ap_id(ap_id) do
Repo.one(from object in Object,
- where: fragment("? @> ?", object.data, ^%{id: ap_id}))
+ where: fragment("(?)->>'id' = ?", object.data, ^ap_id))
end
def get_cached_by_ap_id(ap_id) do
diff --git a/lib/pleroma/plugs/authentication_plug.ex b/lib/pleroma/plugs/authentication_plug.ex
index 14654f2e6..60f6faf49 100644
--- a/lib/pleroma/plugs/authentication_plug.ex
+++ b/lib/pleroma/plugs/authentication_plug.ex
@@ -12,6 +12,7 @@ defmodule Pleroma.Plugs.AuthenticationPlug do
def call(conn, opts) do
with {:ok, username, password} <- decode_header(conn),
{:ok, user} <- opts[:fetcher].(username),
+ false <- !!user.info["deactivated"],
saved_user_id <- get_session(conn, :user_id),
{:ok, verified_user} <- verify(user, password, saved_user_id)
do
@@ -44,7 +45,7 @@ defmodule Pleroma.Plugs.AuthenticationPlug do
defp decode_header(conn) do
with ["Basic " <> header] <- get_req_header(conn, "authorization"),
{:ok, userinfo} <- Base.decode64(header),
- [username, password] <- String.split(userinfo, ":")
+ [username, password] <- String.split(userinfo, ":", parts: 2)
do
{:ok, username, password}
end
diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/plugs/oauth_plug.ex
index fc2a907a2..be737dc9a 100644
--- a/lib/pleroma/plugs/oauth_plug.ex
+++ b/lib/pleroma/plugs/oauth_plug.ex
@@ -9,10 +9,15 @@ defmodule Pleroma.Plugs.OAuthPlug do
end
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
- def call(conn, opts) do
- with ["Bearer " <> header] <- get_req_header(conn, "authorization"),
- %Token{user_id: user_id} <- Repo.get_by(Token, token: header),
- %User{} = user <- Repo.get(User, user_id) do
+ def call(conn, _) do
+ token = case get_req_header(conn, "authorization") do
+ ["Bearer " <> header] -> header
+ _ -> get_session(conn, :oauth_token)
+ end
+ with token when not is_nil(token) <- token,
+ %Token{user_id: user_id} <- Repo.get_by(Token, token: token),
+ %User{} = user <- Repo.get(User, user_id),
+ false <- !!user.info["deactivated"] do
conn
|> assign(:user, user)
else
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 2717377a3..3567c6c88 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -8,11 +8,18 @@ defmodule Pleroma.Upload do
result_file = Path.join(upload_folder, file.filename)
File.cp!(file.path, result_file)
+ # fix content type on some image uploads
+ content_type = if file.content_type == "application/octet-stream" do
+ get_content_type(file.path)
+ else
+ file.content_type
+ end
+
%{
"type" => "Image",
"url" => [%{
"type" => "Link",
- "mediaType" => file.content_type,
+ "mediaType" => content_type,
"href" => url_for(Path.join(uuid, :cow_uri.urlencode(file.filename)))
}],
"name" => file.filename,
@@ -53,4 +60,34 @@ defmodule Pleroma.Upload do
defp url_for(file) do
"#{Web.base_url()}/media/#{file}"
end
+
+ def get_content_type(file) do
+ match = File.open(file, [:read], fn(f) ->
+ case IO.binread(f, 8) do
+ <<0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a>> ->
+ "image/png"
+ <<0x47, 0x49, 0x46, 0x38, _, 0x61, _, _>> ->
+ "image/gif"
+ <<0xff, 0xd8, 0xff, _, _, _, _, _>> ->
+ "image/jpeg"
+ <<0x1a, 0x45, 0xdf, 0xa3, _, _, _, _>> ->
+ "video/webm"
+ <<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70>> ->
+ "video/mp4"
+ <<0x49, 0x44, 0x33, _, _, _, _, _>> ->
+ "audio/mpeg"
+ <<0x4f, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00>> ->
+ "audio/ogg"
+ <<0x52, 0x49, 0x46, 0x46, _, _, _, _>> ->
+ "audio/wav"
+ _ ->
+ "application/octet-stream"
+ end
+ end)
+
+ case match do
+ {:ok, type} -> type
+ _e -> "application/octet-stream"
+ end
+ end
end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index a04bbe276..09bcf0cb4 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -5,8 +5,7 @@ defmodule Pleroma.User do
alias Pleroma.{Repo, User, Object, Web, Activity, Notification}
alias Comeonin.Pbkdf2
alias Pleroma.Web.{OStatus, Websub}
- alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.ActivityPub.{Utils, ActivityPub}
schema "users" do
field :bio, :string
@@ -62,8 +61,9 @@ defmodule Pleroma.User do
end
def user_info(%User{} = user) do
+ oneself = if user.local, do: 1, else: 0
%{
- following_count: length(user.following),
+ following_count: length(user.following) - oneself,
note_count: user.info["note_count"] || 0,
follower_count: user.info["follower_count"] || 0
}
@@ -89,7 +89,7 @@ defmodule Pleroma.User do
end
def update_changeset(struct, params \\ %{}) do
- changeset = struct
+ struct
|> cast(params, [:bio, :name])
|> unique_constraint(:nickname)
|> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/)
@@ -97,6 +97,25 @@ defmodule Pleroma.User do
|> validate_length(:name, min: 1, max: 100)
end
+ def password_update_changeset(struct, params) do
+ changeset = struct
+ |> cast(params, [:password, :password_confirmation])
+ |> validate_required([:password, :password_confirmation])
+ |> validate_confirmation(:password)
+
+ if changeset.valid? do
+ hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
+ changeset
+ |> put_change(:password_hash, hashed)
+ else
+ changeset
+ end
+ end
+
+ def reset_password(user, data) do
+ update_and_set_cache(password_update_changeset(user, data))
+ end
+
def register_changeset(struct, params \\ %{}) do
changeset = struct
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
@@ -123,9 +142,9 @@ defmodule Pleroma.User do
end
end
- def follow(%User{} = follower, %User{} = followed) do
+ def follow(%User{} = follower, %User{info: info} = followed) do
ap_followers = followed.follower_address
- if following?(follower, followed) do
+ if following?(follower, followed) or info["deactivated"] do
{:error,
"Could not follow user: #{followed.nickname} is already on your list."}
else
@@ -138,9 +157,9 @@ defmodule Pleroma.User do
follower = follower
|> follow_changeset(%{following: following})
- |> Repo.update
+ |> update_and_set_cache
- {:ok, followed} = update_follower_count(followed)
+ {:ok, _} = update_follower_count(followed)
follower
end
@@ -148,13 +167,13 @@ defmodule Pleroma.User do
def unfollow(%User{} = follower, %User{} = followed) do
ap_followers = followed.follower_address
- if following?(follower, followed) do
+ if following?(follower, followed) and follower.ap_id != followed.ap_id do
following = follower.following
|> List.delete(ap_followers)
{ :ok, follower } = follower
|> follow_changeset(%{following: following})
- |> Repo.update
+ |> update_and_set_cache
{:ok, followed} = update_follower_count(followed)
@@ -172,6 +191,17 @@ defmodule Pleroma.User do
Repo.get_by(User, ap_id: ap_id)
end
+ def update_and_set_cache(changeset) do
+ with {:ok, user} <- Repo.update(changeset) do
+ Cachex.set(:user_cache, "ap_id:#{user.ap_id}", user)
+ Cachex.set(:user_cache, "nickname:#{user.nickname}", user)
+ Cachex.set(:user_cache, "user_info:#{user.id}", user_info(user))
+ {:ok, user}
+ else
+ e -> e
+ end
+ end
+
def get_cached_by_ap_id(ap_id) do
key = "ap_id:#{ap_id}"
Cachex.get!(:user_cache, key, fallback: fn(_) -> get_by_ap_id(ap_id) end)
@@ -195,7 +225,7 @@ defmodule Pleroma.User do
with %User{} = user <- get_by_nickname(nickname) do
user
else _e ->
- with [nick, domain] <- String.split(nickname, "@"),
+ with [_nick, _domain] <- String.split(nickname, "@"),
{:ok, user} <- OStatus.make_user(nickname) do
user
else _e -> nil
@@ -220,9 +250,18 @@ defmodule Pleroma.User do
{:ok, Repo.all(q)}
end
+ def increase_note_count(%User{} = user) do
+ note_count = (user.info["note_count"] || 0) + 1
+ new_info = Map.put(user.info, "note_count", note_count)
+
+ cs = info_changeset(user, %{info: new_info})
+
+ update_and_set_cache(cs)
+ end
+
def update_note_count(%User{} = user) do
note_count_query = from a in Object,
- where: fragment("? @> ?", a.data, ^%{actor: user.ap_id, type: "Note"}),
+ where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
select: count(a.id)
note_count = Repo.one(note_count_query)
@@ -231,12 +270,13 @@ defmodule Pleroma.User do
cs = info_changeset(user, %{info: new_info})
- Repo.update(cs)
+ update_and_set_cache(cs)
end
def update_follower_count(%User{} = user) do
follower_count_query = from u in User,
where: fragment("? @> ?", u.following, ^user.follower_address),
+ where: u.id != ^user.id,
select: count(u.id)
follower_count = Repo.one(follower_count_query)
@@ -245,14 +285,95 @@ defmodule Pleroma.User do
cs = info_changeset(user, %{info: new_info})
- Repo.update(cs)
+ update_and_set_cache(cs)
end
- def get_notified_from_activity(%Activity{data: %{"to" => to}} = activity) do
+ def get_notified_from_activity(%Activity{data: %{"to" => to}}) do
query = from u in User,
where: u.ap_id in ^to,
where: u.local == true
Repo.all(query)
end
+
+ def get_recipients_from_activity(%Activity{data: %{"to" => to}}) do
+ query = from u in User,
+ where: u.ap_id in ^to,
+ or_where: fragment("? \\\?| ?", u.following, ^to)
+
+ query = from u in query,
+ where: u.local == true
+
+ Repo.all(query)
+ end
+
+ def search(query, resolve) do
+ if resolve do
+ User.get_or_fetch_by_nickname(query)
+ end
+ q = from u in User,
+ where: fragment("(to_tsvector('english', ?) || to_tsvector('english', ?)) @@ plainto_tsquery('english', ?)", u.nickname, u.name, ^query),
+ limit: 20
+ Repo.all(q)
+ end
+
+ def block(user, %{ap_id: ap_id}) do
+ blocks = user.info["blocks"] || []
+ new_blocks = Enum.uniq([ap_id | blocks])
+ new_info = Map.put(user.info, "blocks", new_blocks)
+
+ cs = User.info_changeset(user, %{info: new_info})
+ update_and_set_cache(cs)
+ end
+
+ def unblock(user, %{ap_id: ap_id}) do
+ blocks = user.info["blocks"] || []
+ new_blocks = List.delete(blocks, ap_id)
+ new_info = Map.put(user.info, "blocks", new_blocks)
+
+ cs = User.info_changeset(user, %{info: new_info})
+ update_and_set_cache(cs)
+ end
+
+ def blocks?(user, %{ap_id: ap_id}) do
+ blocks = user.info["blocks"] || []
+ Enum.member?(blocks, ap_id)
+ end
+
+ def local_user_query() do
+ from u in User,
+ where: u.local == true
+ end
+
+ def deactivate (%User{} = user) do
+ new_info = Map.put(user.info, "deactivated", true)
+ cs = User.info_changeset(user, %{info: new_info})
+ update_and_set_cache(cs)
+ end
+
+ def delete (%User{} = user) do
+ {:ok, user} = User.deactivate(user)
+
+ # Remove all relationships
+ {:ok, followers } = User.get_followers(user)
+ followers
+ |> Enum.each(fn (follower) -> User.unfollow(follower, user) end)
+
+ {:ok, friends} = User.get_friends(user)
+ friends
+ |> Enum.each(fn (followed) -> User.unfollow(user, followed) end)
+
+ query = from a in Activity,
+ where: a.actor == ^user.ap_id
+
+ Repo.all(query)
+ |> Enum.each(fn (activity) ->
+ case activity.data["type"] do
+ "Create" -> ActivityPub.delete(Object.get_by_ap_id(activity.data["object"]["id"]))
+ _ -> "Doing nothing" # TODO: Do something with likes, follows, repeats.
+ end
+ end)
+
+ :ok
+ end
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 31aa2c4f1..421fd5cd7 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1,6 +1,5 @@
defmodule Pleroma.Web.ActivityPub.ActivityPub do
- alias Pleroma.{Activity, Repo, Object, Upload, User, Web, Notification}
- alias Ecto.{Changeset, UUID}
+ alias Pleroma.{Activity, Repo, Object, Upload, User, Notification}
import Ecto.Query
import Pleroma.Web.ActivityPub.Utils
require Logger
@@ -9,8 +8,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
with nil <- Activity.get_by_ap_id(map["id"]),
map <- lazy_put_activity_defaults(map),
:ok <- insert_full_object(map) do
- {:ok, activity} = Repo.insert(%Activity{data: map, local: local})
+ {:ok, activity} = Repo.insert(%Activity{data: map, local: local, actor: map["actor"]})
Notification.create_notifications(activity)
+ stream_out(activity)
{:ok, activity}
else
%Activity{} = activity -> {:ok, activity}
@@ -18,6 +18,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
+ def stream_out(activity) do
+ if activity.data["type"] in ["Create", "Announce"] do
+ Pleroma.Web.Streamer.stream("user", activity)
+ if Enum.member?(activity.data["to"], "https://www.w3.org/ns/activitystreams#Public") do
+ Pleroma.Web.Streamer.stream("public", activity)
+ if activity.local do
+ Pleroma.Web.Streamer.stream("public:local", activity)
+ end
+ end
+ end
+ end
+
def create(to, actor, context, object, additional \\ %{}, published \\ nil, local \\ true) do
with create_data <- make_create_data(%{to: to, actor: actor, published: published, context: context, object: object}, additional),
{:ok, activity} <- insert(create_data, local),
@@ -27,7 +39,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
# TODO: This is weird, maybe we shouldn't check here if we can make the activity.
- def like(%User{ap_id: ap_id} = user, %Object{data: %{"id" => id}} = object, activity_id \\ nil, local \\ true) do
+ def like(%User{ap_id: ap_id} = user, %Object{data: %{"id" => _}} = object, activity_id \\ nil, local \\ true) do
with nil <- get_existing_like(ap_id, object),
like_data <- make_like_data(user, object, activity_id),
{:ok, activity} <- insert(like_data, local),
@@ -49,7 +61,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
- def announce(%User{ap_id: ap_id} = user, %Object{data: %{"id" => 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 announce_data <- make_announce_data(user, object, activity_id),
{:ok, activity} <- insert(announce_data, local),
{:ok, object} <- add_announce_to_object(activity, object),
@@ -87,17 +99,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
}
with Repo.delete(object),
Repo.delete_all(Activity.all_non_create_by_object_ap_id_q(id)),
- Repo.delete_all(Activity.all_by_object_ap_id_q(id)),
{:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
end
end
- def fetch_activities_for_context(context) do
+ def fetch_activities_for_context(context, opts \\ %{}) do
query = from activity in Activity,
where: fragment("?->>'type' = ? and ?->>'context' = ?", activity.data, "Create", activity.data, ^context),
order_by: [desc: :id]
+ query = restrict_blocked(query, opts)
Repo.all(query)
end
@@ -137,7 +149,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_actor(query, %{"actor_id" => actor_id}) do
from activity in query,
- where: fragment("?->>'actor' = ?", activity.data, ^actor_id)
+ where: activity.actor == ^actor_id
end
defp restrict_actor(query, _), do: query
@@ -156,10 +168,32 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
defp restrict_favorited_by(query, _), do: query
+ defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
+ from activity in query,
+ where: fragment("not (? #> '{\"object\",\"attachment\"}' = ?)", activity.data, ^[])
+ end
+ defp restrict_media(query, _), do: query
+
+ # Only search through last 100_000 activities by default
+ defp restrict_recent(query, %{"whole_db" => true}), do: query
+ defp restrict_recent(query, _) do
+ since = (Repo.aggregate(Activity, :max, :id) || 0) - 100_000
+
+ from activity in query,
+ where: activity.id > ^since
+ end
+
+ defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
+ blocks = info["blocks"] || []
+ from activity in query,
+ where: fragment("not (? = ANY(?))", activity.actor, ^blocks)
+ end
+ defp restrict_blocked(query, _), do: query
+
def fetch_activities(recipients, opts \\ %{}) do
base_query = from activity in Activity,
limit: 20,
- order_by: [desc: :id]
+ order_by: [fragment("? desc nulls last", activity.id)]
base_query
|> restrict_recipients(recipients)
@@ -170,6 +204,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_actor(opts)
|> restrict_type(opts)
|> restrict_favorited_by(opts)
+ |> restrict_recent(opts)
+ |> restrict_blocked(opts)
+ |> restrict_media(opts)
|> Repo.all
|> Enum.reverse
end
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 4b8e6b690..ac20a2822 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -29,7 +29,12 @@ defmodule Pleroma.Web.ActivityPub.Utils do
Enqueues an activity for federation if it's local
"""
def maybe_federate(%Activity{local: true} = activity) do
- Pleroma.Web.Federator.enqueue(:publish, activity)
+ priority = case activity.data["type"] do
+ "Delete" -> 10
+ "Create" -> 1
+ _ -> 5
+ end
+ Pleroma.Web.Federator.enqueue(:publish, activity, priority)
:ok
end
def maybe_federate(_), do: :ok
@@ -64,7 +69,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
Inserts a full object if it is contained in an activity.
"""
def insert_full_object(%{"object" => object_data}) when is_map(object_data) do
- with {:ok, object} <- Object.create(object_data) do
+ with {:ok, _} <- Object.create(object_data) do
:ok
end
end
@@ -88,9 +93,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do
@doc """
Returns an existing like if a user already liked an object
"""
- def get_existing_like(actor, %{data: %{"id" => id}} = object) do
+ def get_existing_like(actor, %{data: %{"id" => id}}) do
query = from activity in Activity,
- where: fragment("? @> ?", activity.data, ^%{actor: actor, object: id, type: "Like"})
+ where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
+ # this is to use the index
+ where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ?", activity.data, activity.data, ^id),
+ where: fragment("(?)->>'type' = 'Like'", activity.data)
+
Repo.one(query)
end
@@ -197,7 +206,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def make_create_data(params, additional) do
published = params.published || make_date()
- activity = %{
+ %{
"type" => "Create",
"to" => params.to |> Enum.uniq,
"actor" => params.actor.ap_id,
diff --git a/lib/pleroma/web/channels/user_socket.ex b/lib/pleroma/web/channels/user_socket.ex
index 7aa8e556e..4a9bb8e22 100644
--- a/lib/pleroma/web/channels/user_socket.ex
+++ b/lib/pleroma/web/channels/user_socket.ex
@@ -1,8 +1,11 @@
defmodule Pleroma.Web.UserSocket do
use Phoenix.Socket
+ alias Pleroma.User
+ alias Comeonin.Pbkdf2
## Channels
# channel "room:*", Pleroma.Web.RoomChannel
+ channel "chat:*", Pleroma.Web.ChatChannel
## Transports
transport :websocket, Phoenix.Transports.WebSocket
@@ -19,8 +22,13 @@ defmodule Pleroma.Web.UserSocket do
#
# See `Phoenix.Token` documentation for examples in
# performing token verification on connect.
- def connect(_params, socket) do
- {:ok, socket}
+ def connect(%{"token" => token}, socket) do
+ with {:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84600),
+ %User{} = user <- Pleroma.Repo.get(User, user_id) do
+ {:ok, assign(socket, :user_name, user.nickname)}
+ else
+ _e -> :error
+ end
end
# Socket id's are topics that allow you to identify all sockets for a given user:
diff --git a/lib/pleroma/web/chat_channel.ex b/lib/pleroma/web/chat_channel.ex
new file mode 100644
index 000000000..268bef17d
--- /dev/null
+++ b/lib/pleroma/web/chat_channel.ex
@@ -0,0 +1,46 @@
+defmodule Pleroma.Web.ChatChannel do
+ use Phoenix.Channel
+ alias Pleroma.Web.ChatChannel.ChatChannelState
+ alias Pleroma.User
+
+ def join("chat:public", _message, socket) do
+ send(self(), :after_join)
+ {:ok, socket}
+ end
+
+ def handle_info(:after_join, socket) do
+ push socket, "messages", %{messages: ChatChannelState.messages()}
+ {:noreply, socket}
+ end
+
+ def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} = socket) do
+ author = User.get_cached_by_nickname(user_name)
+ author = Pleroma.Web.MastodonAPI.AccountView.render("account.json", user: author)
+ message = ChatChannelState.add_message(%{text: text, author: author})
+
+ broadcast! socket, "new_msg", message
+ {:noreply, socket}
+ end
+end
+
+defmodule Pleroma.Web.ChatChannel.ChatChannelState do
+ use Agent
+ @max_messages 20
+
+ def start_link do
+ Agent.start_link(fn -> %{max_id: 1, messages: []} end, name: __MODULE__)
+ end
+
+ def add_message(message) do
+ Agent.get_and_update(__MODULE__, fn state ->
+ id = state[:max_id] + 1
+ message = Map.put(message, "id", id)
+ messages = [message | state[:messages]] |> Enum.take(@max_messages)
+ {message, %{max_id: id, messages: messages}}
+ end)
+ end
+
+ def messages() do
+ Agent.get(__MODULE__, fn state -> state[:messages] |> Enum.reverse end)
+ end
+end
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index a865cd143..d3a9f7b85 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -16,7 +16,6 @@ defmodule Pleroma.Web.CommonAPI do
def repeat(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
- false <- activity.data["actor"] == user.ap_id,
object <- Object.get_by_ap_id(activity.data["object"]["id"]) do
ActivityPub.announce(user, object)
else
@@ -56,12 +55,14 @@ defmodule Pleroma.Web.CommonAPI do
mentions <- Formatter.parse_mentions(status),
inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]),
to <- to_for_user_and_mentions(user, mentions, inReplyTo),
- tags <- Formatter.parse_tags(status),
- content_html <- make_content_html(status, mentions, attachments, tags),
+ tags <- Formatter.parse_tags(status, data),
+ content_html <- make_content_html(status, mentions, attachments, tags, data["no_attachment_links"]),
context <- make_context(inReplyTo),
- object <- make_note_data(user.ap_id, to, context, content_html, attachments, inReplyTo, tags) do
+ cw <- data["spoiler_text"],
+ object <- make_note_data(user.ap_id, to, context, content_html, attachments, inReplyTo, tags, cw),
+ 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, user, context, object)
- User.update_note_count(user)
+ User.increase_note_count(user)
res
end
end
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index b370a8fb7..e60dff7dc 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -38,15 +38,19 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
end
- def make_content_html(status, mentions, attachments, tags) do
+ def make_content_html(status, mentions, attachments, tags, no_attachment_links \\ false) do
status
|> format_input(mentions, tags)
- |> add_attachments(attachments)
+ |> maybe_add_attachments(attachments, no_attachment_links)
end
def make_context(%Activity{data: %{"context" => context}}), do: context
def make_context(_), do: Utils.generate_context_id
+ def maybe_add_attachments(text, attachments, _no_links = true), do: text
+ def maybe_add_attachments(text, attachments, _no_links) do
+ add_attachments(text, attachments)
+ end
def add_attachments(text, attachments) do
attachment_text = Enum.map(attachments, fn
(%{"url" => [%{"href" => href} | _]}) ->
@@ -54,15 +58,16 @@ defmodule Pleroma.Web.CommonAPI.Utils do
"<a href=\"#{href}\" class='attachment'>#{shortname(name)}</a>"
_ -> ""
end)
- Enum.join([text | attachment_text], "<br>\n")
+ Enum.join([text | attachment_text], "<br>")
end
- def format_input(text, mentions, tags) do
- HtmlSanitizeEx.strip_tags(text)
+ def format_input(text, mentions, _tags) do
+ text
+ |> Formatter.html_escape
|> Formatter.linkify
- |> String.replace("\n", "<br>\n")
+ |> String.replace("\n", "<br>")
|> add_user_links(mentions)
- |> add_tag_links(tags)
+ # |> add_tag_links(tags)
end
def add_tag_links(text, tags) do
@@ -94,11 +99,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end)
end
- def make_note_data(actor, to, context, content_html, attachments, inReplyTo, tags) do
+ def make_note_data(actor, to, context, content_html, attachments, inReplyTo, tags, cw \\ nil) do
object = %{
"type" => "Note",
"to" => to,
"content" => content_html,
+ "summary" => cw,
"context" => context,
"attachment" => attachments,
"actor" => actor,
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index a1b4108cd..b57cf3917 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -2,6 +2,7 @@ defmodule Pleroma.Web.Endpoint do
use Phoenix.Endpoint, otp_app: :pleroma
socket "/socket", Pleroma.Web.UserSocket
+ socket "/api/v1", Pleroma.Web.MastodonAPI.MastodonSocket
# Serve at "/" the static files from "priv/static" directory.
#
@@ -11,7 +12,7 @@ defmodule Pleroma.Web.Endpoint do
at: "/media", from: "uploads", gzip: false
plug Plug.Static,
at: "/", from: :pleroma,
- only: ~w(index.html static finmoji emoji)
+ only: ~w(index.html static finmoji emoji packs sounds sw.js)
# Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint.
diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex
index 4d6ebff8e..b23ed5fcc 100644
--- a/lib/pleroma/web/federator/federator.ex
+++ b/lib/pleroma/web/federator/federator.ex
@@ -14,7 +14,10 @@ defmodule Pleroma.Web.Federator do
Process.sleep(1000 * 60 * 1) # 1 minute
enqueue(:refresh_subscriptions, nil)
end)
- GenServer.start_link(__MODULE__, {:sets.new(), :queue.new()}, name: __MODULE__)
+ GenServer.start_link(__MODULE__, %{
+ in: {:sets.new(), []},
+ out: {:sets.new(), []}
+ }, name: __MODULE__)
end
def handle(:refresh_subscriptions, _) do
@@ -71,22 +74,22 @@ defmodule Pleroma.Web.Federator do
end
end
- def handle(type, payload) do
+ def handle(type, _) do
Logger.debug(fn -> "Unknown task: #{type}" end)
{:error, "Don't know what do do with this"}
end
- def enqueue(type, payload) do
+ def enqueue(type, payload, priority \\ 1) do
if Mix.env == :test do
handle(type, payload)
else
- GenServer.cast(__MODULE__, {:enqueue, type, payload})
+ GenServer.cast(__MODULE__, {:enqueue, type, payload, priority})
end
end
def maybe_start_job(running_jobs, queue) do
- if (:sets.size(running_jobs) < @max_jobs) && !:queue.is_empty(queue) do
- {{:value, {type, payload}}, queue} = :queue.out(queue)
+ if (:sets.size(running_jobs) < @max_jobs) && queue != [] do
+ {{type, payload}, queue} = queue_pop(queue)
{:ok, pid} = Task.start(fn -> handle(type, payload) end)
mref = Process.monitor(pid)
{:sets.add_element(mref, running_jobs), queue}
@@ -95,20 +98,41 @@ defmodule Pleroma.Web.Federator do
end
end
- def handle_cast({:enqueue, type, payload}, {running_jobs, queue}) do
- queue = :queue.in({type, payload}, queue)
- {running_jobs, queue} = maybe_start_job(running_jobs, queue)
- {:noreply, {running_jobs, queue}}
+ def handle_cast({:enqueue, type, payload, priority}, state) when type in [:incoming_doc] do
+ %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state
+ i_queue = enqueue_sorted(i_queue, {type, payload}, 1)
+ {i_running_jobs, i_queue} = maybe_start_job(i_running_jobs, i_queue)
+ {:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}}
end
- def handle_info({:DOWN, ref, :process, _pid, _reason}, {running_jobs, queue}) do
- running_jobs = :sets.del_element(ref, running_jobs)
- {running_jobs, queue} = maybe_start_job(running_jobs, queue)
- {:noreply, {running_jobs, queue}}
+ def handle_cast({:enqueue, type, payload, priority}, state) do
+ %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state
+ o_queue = enqueue_sorted(o_queue, {type, payload}, 1)
+ {o_running_jobs, o_queue} = maybe_start_job(o_running_jobs, o_queue)
+ {:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}}
end
def handle_cast(m, state) do
IO.inspect("Unknown: #{inspect(m)}, #{inspect(state)}")
{:noreply, state}
end
+
+ def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do
+ %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state
+ i_running_jobs = :sets.del_element(ref, i_running_jobs)
+ o_running_jobs = :sets.del_element(ref, o_running_jobs)
+ {i_running_jobs, i_queue} = maybe_start_job(i_running_jobs, i_queue)
+ {o_running_jobs, o_queue} = maybe_start_job(o_running_jobs, o_queue)
+
+ {:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}}
+ end
+
+ def enqueue_sorted(queue, element, priority) do
+ [%{item: element, priority: priority} | queue]
+ |> Enum.sort_by(fn (%{priority: priority}) -> priority end)
+ end
+
+ def queue_pop([%{item: element} | queue]) do
+ {element, queue}
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index a01a199fb..e50f53ba4 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1,14 +1,14 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller
alias Pleroma.{Repo, Activity, User, Notification}
- alias Pleroma.Web.OAuth.App
alias Pleroma.Web
- alias Pleroma.Web.MastodonAPI.{StatusView, AccountView}
+ alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView}
alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.TwitterAPI.TwitterAPI
- alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.{CommonAPI, OStatus}
+ alias Pleroma.Web.OAuth.{Authorization, Token, App}
+ alias Comeonin.Pbkdf2
import Ecto.Query
- import Logger
+ require Logger
def create_app(conn, params) do
with cs <- App.register_changeset(%App{}, params) |> IO.inspect,
@@ -23,7 +23,58 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
- def verify_credentials(%{assigns: %{user: user}} = conn, params) do
+ def update_credentials(%{assigns: %{user: user}} = conn, params) do
+ params = if bio = params["note"] do
+ Map.put(params, "bio", bio)
+ else
+ params
+ end
+
+ params = if name = params["display_name"] do
+ Map.put(params, "name", name)
+ else
+ params
+ end
+
+ user = if avatar = params["avatar"] do
+ with %Plug.Upload{} <- avatar,
+ {:ok, object} <- ActivityPub.upload(avatar),
+ change = Ecto.Changeset.change(user, %{avatar: object.data}),
+ {:ok, user} = Repo.update(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} <- Repo.update(change) do
+ user
+ else
+ _e -> user
+ end
+ else
+ user
+ end
+
+ with changeset <- User.update_changeset(user, params),
+ {:ok, user} <- Repo.update(changeset) do
+ json conn, AccountView.render("account.json", %{user: user})
+ else
+ _e ->
+ conn
+ |> put_status(403)
+ |> json(%{error: "Invalid request"})
+ end
+ end
+
+ def verify_credentials(%{assigns: %{user: user}} = conn, _) do
account = AccountView.render("account.json", %{user: user})
json(conn, account)
end
@@ -42,6 +93,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
@instance Application.get_env(:pleroma, :instance)
def masto_instance(conn, _params) do
+ user_count = Repo.aggregate(User.local_user_query, :count, :id)
response = %{
uri: Web.base_url,
title: Keyword.get(@instance, :name),
@@ -52,15 +104,33 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
streaming_api: String.replace(Web.base_url, ["http","https"], "wss")
},
stats: %{
- user_count: 1,
status_count: 2,
+ user_count: user_count,
domain_count: 3
- }
+ },
+ max_toot_chars: Keyword.get(@instance, :limit)
}
json(conn, response)
end
+ defp mastodonized_emoji do
+ Pleroma.Formatter.get_custom_emoji()
+ |> Enum.map(fn {shortcode, relative_url} ->
+ url = to_string URI.merge(Web.base_url(), relative_url)
+ %{
+ "shortcode" => shortcode,
+ "static_url" => url,
+ "url" => url
+ }
+ end)
+ end
+
+ def custom_emojis(conn, _params) do
+ mastodon_emoji = mastodonized_emoji()
+ json conn, mastodon_emoji
+ end
+
defp add_link_headers(conn, method, activities) do
last = List.last(activities)
first = List.first(activities)
@@ -79,6 +149,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def home_timeline(%{assigns: %{user: user}} = conn, params) do
params = params
|> Map.put("type", ["Create", "Announce"])
+ |> Map.put("blocking_user", user)
activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
|> Enum.reverse
@@ -92,6 +163,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
params = params
|> Map.put("type", ["Create", "Announce"])
|> Map.put("local_only", !!params["local"])
+ |> Map.put("blocking_user", user)
activities = ActivityPub.fetch_public_activities(params)
|> Enum.reverse
@@ -107,6 +179,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
params = params
|> Map.put("type", ["Create", "Announce"])
|> Map.put("actor_id", ap_id)
+ |> Map.put("whole_db", true)
activities = ActivityPub.fetch_activities([], params)
|> Enum.reverse
@@ -123,8 +196,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Repo.get(Activity, id),
- activities <- ActivityPub.fetch_activities_for_context(activity.data["object"]["context"]),
+ activities <- ActivityPub.fetch_activities_for_context(activity.data["object"]["context"], %{"blocking_user" => user}),
activities <- activities |> Enum.filter(fn (%{id: aid}) -> to_string(aid) != to_string(id) end),
+ activities <- activities |> Enum.filter(fn (%{data: %{"type" => type}}) -> type == "Create" end),
grouped_activities <- Enum.group_by(activities, fn (%{id: id}) -> id < activity.id end) do
result = %{
ancestors: StatusView.render("index.json", for: user, activities: grouped_activities[true] || [], as: :activity) |> Enum.reverse,
@@ -135,9 +209,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
- def post_status(%{assigns: %{user: user}} = conn, %{"status" => status} = params) do
+ def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
params = params
|> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
+ |> Map.put("no_attachment_links", true)
{:ok, activity} = CommonAPI.post(user, params)
render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}
@@ -155,9 +230,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
- with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(ap_id_or_id, user),
- %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
- render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}
+ with {:ok, announce, _activity} = CommonAPI.repeat(ap_id_or_id, user) do
+ render conn, StatusView, "status.json", %{activity: announce, for: user, as: :activity}
end
end
@@ -177,23 +251,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def notifications(%{assigns: %{user: user}} = conn, params) do
notifications = Notification.for_user(user, params)
- result = Enum.map(notifications, fn (%{id: id, activity: activity, inserted_at: created_at}) ->
- actor = User.get_cached_by_ap_id(activity.data["actor"])
- created_at = NaiveDateTime.to_iso8601(created_at)
- |> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
- case activity.data["type"] do
- "Create" ->
- %{id: id, type: "mention", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: activity})}
- "Like" ->
- liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
- %{id: id, type: "favourite", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: liked_activity})}
- "Announce" ->
- announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
- %{id: id, type: "reblog", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: announced_activity})}
- "Follow" ->
- %{id: id, type: "follow", created_at: created_at, account: AccountView.render("account.json", %{user: actor})}
- _ -> nil
- end
+ result = Enum.map(notifications, fn x ->
+ render_notification(user, x)
end)
|> Enum.filter(&(&1))
@@ -202,6 +261,33 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> json(result)
end
+ def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
+ with {:ok, notification} <- Notification.get(user, id) do
+ json(conn, render_notification(user, notification))
+ else
+ {:error, reason} ->
+ conn
+ |> put_resp_content_type("application/json")
+ |> send_resp(403, Poison.encode!(%{"error" => reason}))
+ end
+ end
+
+ def clear_notifications(%{assigns: %{user: user}} = conn, _params) do
+ Notification.clear(user)
+ json(conn, %{})
+ end
+
+ def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
+ with {:ok, _notif} <- Notification.dismiss(user, id) do
+ json(conn, %{})
+ else
+ {:error, reason} ->
+ conn
+ |> put_resp_content_type("application/json")
+ |> send_resp(403, Poison.encode!(%{"error" => reason}))
+ end
+ end
+
def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
id = List.wrap(id)
q = from u in User,
@@ -210,7 +296,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
render conn, AccountView, "relationships.json", %{user: user, targets: targets}
end
- def upload(%{assigns: %{user: user}} = conn, %{"file" => file}) do
+ def upload(%{assigns: %{user: _}} = conn, %{"file" => file}) do
with {:ok, object} <- ActivityPub.upload(file) do
data = object.data
|> Map.put("id", object.id)
@@ -220,7 +306,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def favourited_by(conn, %{"id" => id}) do
- with %Activity{data: %{"object" => %{"likes" => likes} = data}} <- Repo.get(Activity, id) do
+ with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do
q = from u in User,
where: u.ap_id in ^likes
users = Repo.all(q)
@@ -246,6 +332,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
params = params
|> Map.put("type", "Create")
|> Map.put("local_only", !!params["local"])
+ |> Map.put("blocking_user", user)
activities = ActivityPub.fetch_public_activities(params)
|> Enum.reverse
@@ -271,9 +358,27 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
with %User{} = followed <- Repo.get(User, id),
- {:ok, follower} <- User.follow(follower, followed),
- {:ok, activity} <- ActivityPub.follow(follower, followed) do
+ {:ok, follower} <- User.follow(follower, followed),
+ {:ok, _activity} <- ActivityPub.follow(follower, followed) do
render conn, AccountView, "relationship.json", %{user: follower, target: followed}
+ else
+ {:error, message} ->
+ conn
+ |> put_resp_content_type("application/json")
+ |> send_resp(403, Poison.encode!(%{"error" => message}))
+ end
+ end
+
+ def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
+ with %User{} = followed <- Repo.get_by(User, nickname: uri),
+ {:ok, follower} <- User.follow(follower, followed),
+ {:ok, _activity} <- ActivityPub.follow(follower, followed) do
+ render conn, AccountView, "account.json", %{user: followed}
+ else
+ {:error, message} ->
+ conn
+ |> put_resp_content_type("application/json")
+ |> send_resp(403, Poison.encode!(%{"error" => message}))
end
end
@@ -290,21 +395,55 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
- def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
- if params["resolve"] == "true" do
- User.get_or_fetch_by_nickname(query)
+ def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
+ with %User{} = blocked <- Repo.get(User, id),
+ {:ok, blocker} <- User.block(blocker, blocked) do
+ render conn, AccountView, "relationship.json", %{user: blocker, target: blocked}
+ else
+ {:error, message} ->
+ conn
+ |> put_resp_content_type("application/json")
+ |> send_resp(403, Poison.encode!(%{"error" => message}))
end
+ end
- q = from u in User,
- where: fragment("(to_tsvector('english', ?) || to_tsvector('english', ?)) @@ plainto_tsquery('english', ?)", u.nickname, u.name, ^query),
- limit: 20
- accounts = Repo.all(q)
+ def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
+ with %User{} = blocked <- Repo.get(User, id),
+ {:ok, blocker} <- User.unblock(blocker, blocked) do
+ render conn, AccountView, "relationship.json", %{user: blocker, target: blocked}
+ else
+ {:error, message} ->
+ conn
+ |> put_resp_content_type("application/json")
+ |> send_resp(403, Poison.encode!(%{"error" => message}))
+ end
+ end
+
+ # TODO: Use proper query
+ def blocks(%{assigns: %{user: user}} = conn, _) do
+ with blocked_users <- user.info["blocks"] || [],
+ accounts <- Enum.map(blocked_users, fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end) do
+ res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
+ json(conn, res)
+ end
+ end
+
+ def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
+ accounts = User.search(query, params["resolve"] == "true")
+
+ fetched = if Regex.match?(~r/https?:/, query) do
+ with {:ok, activities} <- OStatus.fetch_activity_from_url(query) do
+ activities
+ else
+ _e -> []
+ end
+ end || []
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)
+ statuses = Repo.all(q) ++ fetched
res = %{
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
@@ -315,10 +454,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
json(conn, res)
end
- def favourites(%{assigns: %{user: user}} = conn, params) do
+ def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
+ accounts = User.search(query, params["resolve"] == "true")
+
+ res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
+
+ json(conn, res)
+ end
+
+ def favourites(%{assigns: %{user: user}} = conn, _) do
params = conn
|> Map.put("type", "Create")
|> Map.put("favorited_by", user.ap_id)
+ |> Map.put("blocking_user", user)
activities = ActivityPub.fetch_activities([], params)
|> Enum.reverse
@@ -327,6 +475,127 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
end
+ def index(%{assigns: %{user: user}} = conn, _params) do
+ token = conn
+ |> get_session(:oauth_token)
+
+ if user && token do
+ mastodon_emoji = mastodonized_emoji()
+ accounts = Map.put(%{}, user.id, AccountView.render("account.json", %{user: user}))
+ initial_state = %{
+ meta: %{
+ streaming_api_base_url: String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
+ access_token: token,
+ locale: "en",
+ domain: Pleroma.Web.Endpoint.host(),
+ admin: "1",
+ me: "#{user.id}",
+ unfollow_modal: false,
+ boost_modal: false,
+ delete_modal: true,
+ auto_play_gif: false,
+ reduce_motion: false
+ },
+ compose: %{
+ me: "#{user.id}",
+ default_privacy: "public",
+ default_sensitive: false
+ },
+ media_attachments: %{
+ accept_content_types: [
+ ".jpg",
+ ".jpeg",
+ ".png",
+ ".gif",
+ ".webm",
+ ".mp4",
+ ".m4v",
+ "image\/jpeg",
+ "image\/png",
+ "image\/gif",
+ "video\/webm",
+ "video\/mp4"
+ ]
+ },
+ settings: %{
+ onboarded: true,
+ home: %{
+ shows: %{
+ reblog: true,
+ reply: true
+ }
+ },
+ notifications: %{
+ alerts: %{
+ follow: true,
+ favourite: true,
+ reblog: true,
+ mention: true
+ },
+ shows: %{
+ follow: true,
+ favourite: true,
+ reblog: true,
+ mention: true
+ },
+ sounds: %{
+ follow: true,
+ favourite: true,
+ reblog: true,
+ mention: true
+ }
+ }
+ },
+ push_subscription: nil,
+ accounts: accounts,
+ custom_emojis: mastodon_emoji
+ } |> Poison.encode!
+ conn
+ |> put_layout(false)
+ |> render(MastodonView, "index.html", %{initial_state: initial_state})
+ else
+ conn
+ |> redirect(to: "/web/login")
+ end
+ end
+
+ def login(conn, _) do
+ conn
+ |> render(MastodonView, "login.html", %{error: false})
+ end
+
+ defp get_or_make_app() do
+ with %App{} = app <- Repo.get_by(App, client_name: "Mastodon-Local") do
+ {:ok, app}
+ else
+ _e ->
+ cs = App.register_changeset(%App{}, %{client_name: "Mastodon-Local", redirect_uris: ".", scopes: "read,write,follow"})
+ Repo.insert(cs)
+ end
+ end
+
+ def login_post(conn, %{"authorization" => %{ "name" => name, "password" => password}}) do
+ with %User{} = user <- User.get_cached_by_nickname(name),
+ true <- Pbkdf2.checkpw(password, user.password_hash),
+ {:ok, app} <- get_or_make_app(),
+ {:ok, auth} <- Authorization.create_authorization(app, user),
+ {:ok, token} <- Token.exchange_token(app, auth) do
+ conn
+ |> put_session(:oauth_token, token.token)
+ |> redirect(to: "/web/getting-started")
+ else
+ _e ->
+ conn
+ |> render(MastodonView, "login.html", %{error: "Wrong username or password"})
+ end
+ end
+
+ def logout(conn, _) do
+ conn
+ |> clear_session
+ |> redirect(to: "/")
+ end
+
def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
Logger.debug("Unimplemented, returning unmodified relationship")
with %User{} = target <- Repo.get(User, id) do
@@ -338,4 +607,23 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
Logger.debug("Unimplemented, returning an empty array")
json(conn, [])
end
+
+ def render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do
+ actor = User.get_cached_by_ap_id(activity.data["actor"])
+ created_at = NaiveDateTime.to_iso8601(created_at)
+ |> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
+ case activity.data["type"] do
+ "Create" ->
+ %{id: id, type: "mention", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: activity, for: user})}
+ "Like" ->
+ liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
+ %{id: id, type: "favourite", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: liked_activity, for: user})}
+ "Announce" ->
+ announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
+ %{id: id, type: "reblog", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: announced_activity, for: user})}
+ "Follow" ->
+ %{id: id, type: "follow", created_at: created_at, account: AccountView.render("account.json", %{user: actor})}
+ _ -> nil
+ end
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/mastodon_socket.ex b/lib/pleroma/web/mastodon_api/mastodon_socket.ex
new file mode 100644
index 000000000..fe71ea271
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/mastodon_socket.ex
@@ -0,0 +1,41 @@
+defmodule Pleroma.Web.MastodonAPI.MastodonSocket do
+ use Phoenix.Socket
+
+ alias Pleroma.Web.OAuth.Token
+ alias Pleroma.{User, Repo}
+
+ transport :streaming, Phoenix.Transports.WebSocket.Raw,
+ timeout: :infinity # We never receive data.
+
+ def connect(params, socket) do
+ with token when not is_nil(token) <- params["access_token"],
+ %Token{user_id: user_id} <- Repo.get_by(Token, token: token),
+ %User{} = user <- Repo.get(User, user_id),
+ stream when stream in ["public", "public:local", "user"] <- params["stream"] do
+ socket = socket
+ |> assign(:topic, params["stream"])
+ |> assign(:user, user)
+ Pleroma.Web.Streamer.add_socket(params["stream"], socket)
+ {:ok, socket}
+ else
+ _e -> :error
+ end
+ end
+
+ def id(_), do: nil
+
+ def handle(:text, message, _state) do
+ IO.inspect message
+ #| :ok
+ #| state
+ #| {:text, message}
+ #| {:text, message, state}
+ #| {:close, "Goodbye!"}
+ {:text, message}
+ end
+
+ def handle(:closed, _, %{socket: socket}) do
+ topic = socket.assigns[:topic]
+ Pleroma.Web.Streamer.remove_socket(topic, socket)
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index ff02587d6..02f1e60bb 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -4,7 +4,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.CommonAPI.Utils
- defp image_url(%{"url" => [ %{ "href" => href } | t ]}), do: href
+ defp image_url(%{"url" => [ %{ "href" => href } | _ ]}), do: href
defp image_url(_), do: nil
def render("accounts.json", %{users: users} = opts) do
@@ -18,7 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
header = image_url(user.info["banner"]) || "https://placehold.it/700x335"
%{
- id: user.id,
+ id: to_string(user.id),
username: hd(String.split(user.nickname, "@")),
acct: user.nickname,
display_name: user.name,
@@ -43,7 +43,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
def render("mention.json", %{user: user}) do
%{
- id: user.id,
+ id: to_string(user.id),
acct: user.nickname,
username: hd(String.split(user.nickname, "@")),
url: user.ap_id
@@ -52,10 +52,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
def render("relationship.json", %{user: user, target: target}) do
%{
- id: target.id,
+ id: to_string(target.id),
following: User.following?(user, target),
followed_by: User.following?(target, user),
- blocking: false,
+ blocking: User.blocks?(user, target),
muting: false,
requested: false,
domain_blocking: false
diff --git a/lib/pleroma/web/mastodon_api/views/mastodon_view.ex b/lib/pleroma/web/mastodon_api/views/mastodon_view.ex
new file mode 100644
index 000000000..370fad374
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/mastodon_view.ex
@@ -0,0 +1,5 @@
+defmodule Pleroma.Web.MastodonAPI.MastodonView do
+ use Pleroma.Web, :view
+ import Phoenix.HTML
+ import Phoenix.HTML.Form
+end
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index cce4f7217..5585a5605 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -21,9 +21,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|> Enum.map(fn (user) -> AccountView.render("mention.json", %{user: user}) end)
%{
- id: activity.id,
+ id: to_string(activity.id),
uri: object,
- url: nil,
+ url: nil, # TODO: This might be wrong, check with mastodon.
account: AccountView.render("account.json", %{user: user}),
in_reply_to_id: nil,
in_reply_to_account_id: nil,
@@ -45,7 +45,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
name: "Web",
website: nil
},
- language: nil
+ language: nil,
+ emojis: []
}
end
@@ -74,10 +75,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
reply_to = Activity.get_create_activity_by_object_ap_id(object["inReplyTo"])
reply_to_user = reply_to && User.get_cached_by_ap_id(reply_to.data["actor"])
+ emojis = (activity.data["object"]["emoji"] || [])
+ |> Enum.map(fn {name, url} -> %{ shortcode: name, url: url, static_url: url } end)
+
%{
- id: activity.id,
+ id: to_string(activity.id),
uri: object["id"],
- url: object["external_url"],
+ url: object["external_url"] || object["id"],
account: AccountView.render("account.json", %{user: user}),
in_reply_to_id: reply_to && reply_to.id,
in_reply_to_account_id: reply_to_user && reply_to_user.id,
@@ -90,16 +94,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
favourited: !!favorited,
muted: false,
sensitive: sensitive,
- spoiler_text: "",
+ spoiler_text: object["summary"] || "",
visibility: "public",
- media_attachments: attachments,
+ media_attachments: attachments |> Enum.take(4),
mentions: mentions,
tags: [], # fix,
application: %{
name: "Web",
website: nil
},
- language: nil
+ language: nil,
+ emojis: emojis
}
end
@@ -115,7 +120,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
<< hash_id::signed-32, _rest::binary >> = :crypto.hash(:md5, href)
%{
- id: attachment["id"] || hash_id,
+ id: to_string(attachment["id"] || hash_id),
url: href,
remote_url: href,
preview_url: href,
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 3e66c3ee8..e8483dec0 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -25,7 +25,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
auth: auth
}
else
- url = "#{redirect_uri}?code=#{auth.token}"
+ connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
+ url = "#{redirect_uri}#{connector}code=#{auth.token}"
url = if params["state"] do
url <> "&state=#{params["state"]}"
else
@@ -40,7 +41,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
# - proper scope handling
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
with %App{} = app <- Repo.get_by(App, client_id: params["client_id"], client_secret: params["client_secret"]),
- %Authorization{} = auth <- Repo.get_by(Authorization, token: params["code"], app_id: app.id),
+ fixed_token = fix_padding(params["code"]),
+ %Authorization{} = auth <- Repo.get_by(Authorization, token: fixed_token, app_id: app.id),
{:ok, token} <- Token.exchange_token(app, auth) do
response = %{
token_type: "Bearer",
@@ -50,6 +52,14 @@ defmodule Pleroma.Web.OAuth.OAuthController do
scope: "read write follow"
}
json(conn, response)
+ else
+ _error -> json(conn, %{error: "Invalid credentials"})
end
end
+
+ defp fix_padding(token) do
+ token
+ |> Base.url_decode64!(padding: false)
+ |> Base.url_encode64
+ end
end
diff --git a/lib/pleroma/web/ostatus/activity_representer.ex b/lib/pleroma/web/ostatus/activity_representer.ex
index 2092ab7fa..aa2b1df39 100644
--- a/lib/pleroma/web/ostatus/activity_representer.ex
+++ b/lib/pleroma/web/ostatus/activity_representer.ex
@@ -56,9 +56,9 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
defp get_links(_activity), do: []
- defp get_emoji_links(content) do
- Enum.map(Formatter.get_emoji(content), fn({emoji, file}) ->
- {:link, [name: to_charlist(emoji), rel: 'emoji', href: to_charlist("#{Pleroma.Web.Endpoint.static_url}#{file}")], []}
+ defp get_emoji_links(emojis) do
+ Enum.map(emojis, fn({emoji, file}) ->
+ {:link, [name: to_charlist(emoji), rel: 'emoji', href: to_charlist(file)], []}
end)
end
@@ -81,7 +81,13 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
categories = (activity.data["object"]["tag"] || [])
|> Enum.map(fn (tag) -> {:category, [term: to_charlist(tag)], []} end)
- emoji_links = get_emoji_links(activity.data["object"]["content"] || "")
+ emoji_links = get_emoji_links(activity.data["object"]["emoji"] || %{})
+
+ summary = if activity.data["object"]["summary"] do
+ [{:summary, [], h.(activity.data["object"]["summary"])}]
+ else
+ []
+ end
[
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/note']},
@@ -93,7 +99,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
{:updated, h.(updated_at)},
{:"ostatus:conversation", [ref: h.(activity.data["context"])], h.(activity.data["context"])},
{:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []},
- ] ++ 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
def to_simple_form(%{data: %{"type" => "Like"}} = activity, user, with_author) do
@@ -102,7 +108,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
updated_at = activity.data["published"]
inserted_at = activity.data["published"]
- 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: []
mentions = activity.data["to"] |> get_mentions
@@ -130,7 +136,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
updated_at = activity.data["published"]
inserted_at = activity.data["published"]
- 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: []
retweeted_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
@@ -227,6 +233,8 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
] ++ author
end
+ def to_simple_form(_, _, _), do: nil
+
def wrap_with_entry(simple_form) do
[{
:entry, [
@@ -238,6 +246,4 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
], simple_form
}]
end
-
- def to_simple_form(_, _, _), do: nil
end
diff --git a/lib/pleroma/web/ostatus/feed_representer.ex b/lib/pleroma/web/ostatus/feed_representer.ex
index 6b67b8ddf..08710f246 100644
--- a/lib/pleroma/web/ostatus/feed_representer.ex
+++ b/lib/pleroma/web/ostatus/feed_representer.ex
@@ -2,7 +2,7 @@ defmodule Pleroma.Web.OStatus.FeedRepresenter do
alias Pleroma.Web.OStatus
alias Pleroma.Web.OStatus.{UserRepresenter, ActivityRepresenter}
- 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
|> NaiveDateTime.to_iso8601
diff --git a/lib/pleroma/web/ostatus/handlers/delete_handler.ex b/lib/pleroma/web/ostatus/handlers/delete_handler.ex
index 29fe4052c..4f3016b65 100644
--- a/lib/pleroma/web/ostatus/handlers/delete_handler.ex
+++ b/lib/pleroma/web/ostatus/handlers/delete_handler.ex
@@ -1,10 +1,10 @@
defmodule Pleroma.Web.OStatus.DeleteHandler do
require Logger
- alias Pleroma.Web.{XML, OStatus}
- alias Pleroma.{Activity, Object, Repo}
+ alias Pleroma.Web.XML
+ alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ActivityPub
- def handle_delete(entry, doc \\ nil) do
+ def handle_delete(entry, _doc \\ nil) do
with id <- XML.string_from_xpath("//id", entry),
object when not is_nil(object) <- Object.get_by_ap_id(id),
{:ok, delete} <- ActivityPub.delete(object, false) do
diff --git a/lib/pleroma/web/ostatus/handlers/note_handler.ex b/lib/pleroma/web/ostatus/handlers/note_handler.ex
index dda5c7d5e..8747dbb67 100644
--- a/lib/pleroma/web/ostatus/handlers/note_handler.ex
+++ b/lib/pleroma/web/ostatus/handlers/note_handler.ex
@@ -94,6 +94,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
[author] <- :xmerl_xpath.string('//author[1]', doc),
{:ok, actor} <- OStatus.find_make_or_update_user(author),
content_html <- OStatus.get_content(entry),
+ cw <- OStatus.get_cw(entry),
inReplyTo <- XML.string_from_xpath("//thr:in-reply-to[1]/@ref", entry),
inReplyToActivity <- fetch_replied_to_activity(entry, inReplyTo),
inReplyTo <- (inReplyToActivity && inReplyToActivity.data["object"]["id"]) || inReplyTo,
@@ -103,7 +104,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
mentions <- get_mentions(entry),
to <- make_to_list(actor, mentions),
date <- XML.string_from_xpath("//published", entry),
- note <- CommonAPI.Utils.make_note_data(actor.ap_id, to, context, content_html, attachments, inReplyToActivity, []),
+ note <- CommonAPI.Utils.make_note_data(actor.ap_id, to, context, content_html, attachments, inReplyToActivity, [], cw),
note <- note |> Map.put("id", id) |> Map.put("tag", tags),
note <- note |> Map.put("published", date),
note <- note |> Map.put("emoji", get_emoji(entry)),
@@ -112,7 +113,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
note <- (if inReplyTo && !inReplyToActivity, do: note |> Map.put("inReplyTo", inReplyTo), else: note)
do
res = ActivityPub.create(to, actor, context, note, %{}, date, false)
- User.update_note_count(actor)
+ User.increase_note_count(actor)
res
else
%Activity{} = activity -> {:ok, activity}
diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex
index bc975f82d..745539b3e 100644
--- a/lib/pleroma/web/ostatus/ostatus.ex
+++ b/lib/pleroma/web/ostatus/ostatus.ex
@@ -7,7 +7,6 @@ defmodule Pleroma.Web.OStatus do
alias Pleroma.{Repo, User, Web, Object, Activity}
alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.{WebFinger, Websub}
alias Pleroma.Web.OStatus.{FollowHandler, NoteHandler, DeleteHandler}
@@ -112,7 +111,7 @@ defmodule Pleroma.Web.OStatus do
with id when not is_nil(id) <- string_from_xpath("//activity:object[1]/id", entry),
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
{:ok, activity}
- else e ->
+ else _ ->
Logger.debug("Couldn't get, will try to fetch")
with href when not is_nil(href) <- string_from_xpath("//activity:object[1]/link[@type=\"text/html\"]/@href", entry),
{:ok, [favorited_activity]} <- fetch_activity_from_url(href) do
@@ -150,22 +149,28 @@ defmodule Pleroma.Web.OStatus do
end
@doc """
- Gets the content from a an entry. Will add the cw text to the body for cw'd
- Mastodon notes.
+ Gets the content from a an entry.
"""
def get_content(entry) do
- base_content = string_from_xpath("//content", entry)
+ string_from_xpath("//content", entry)
+ end
+ @doc """
+ Get the cw that mastodon uses.
+ """
+ def get_cw(entry) do
with scope when not is_nil(scope) <- string_from_xpath("//mastodon:scope", entry),
cw when not is_nil(cw) <- string_from_xpath("/*/summary", entry) do
- "<span class='mastodon-cw'>#{cw}</span><br>#{base_content}"
- else _e -> base_content
+ cw
+ else _e -> nil
end
end
def get_tags(entry) do
:xmerl_xpath.string('//category', entry)
- |> Enum.map(fn (category) -> string_from_xpath("/category/@term", category) |> String.downcase end)
+ |> Enum.map(fn (category) -> string_from_xpath("/category/@term", category) end)
+ |> Enum.filter(&(&1))
+ |> Enum.map(&String.downcase/1)
end
def maybe_update(doc, user) do
@@ -185,7 +190,7 @@ defmodule Pleroma.Web.OStatus do
false <- new_data == old_data do
change = Ecto.Changeset.change(user, new_data)
Repo.update(change)
- else e ->
+ else _ ->
{:ok, user}
end
end
@@ -215,7 +220,7 @@ defmodule Pleroma.Web.OStatus do
Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
end
- def make_user(uri) do
+ def make_user(uri, update \\ false) do
with {:ok, info} <- gather_user_info(uri) do
data = %{
name: info["name"],
@@ -225,7 +230,8 @@ defmodule Pleroma.Web.OStatus do
avatar: info["avatar"],
bio: info["bio"]
}
- with %User{} = user <- User.get_by_ap_id(data.ap_id) do
+ with false <- update,
+ %User{} = user <- User.get_by_ap_id(data.ap_id) do
{:ok, user}
else _e -> insert_or_update_user(data)
end
diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex
index 4e3fbb4f6..d442d16fd 100644
--- a/lib/pleroma/web/ostatus/ostatus_controller.ex
+++ b/lib/pleroma/web/ostatus/ostatus_controller.ex
@@ -5,6 +5,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Web.OStatus.{FeedRepresenter, ActivityRepresenter}
alias Pleroma.Repo
alias Pleroma.Web.{OStatus, Federator}
+ alias Pleroma.Web.XML
import Ecto.Query
def feed_redirect(conn, %{"nickname" => nickname}) do
@@ -36,10 +37,26 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|> send_resp(200, response)
end
- def salmon_incoming(conn, params) do
+ defp decode_or_retry(body) do
+ with {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
+ {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
+ {:ok, doc}
+ else
+ _e ->
+ with [decoded | _] <- Pleroma.Web.Salmon.decode(body),
+ doc <- XML.parse_document(decoded),
+ uri when not is_nil(uri) <- XML.string_from_xpath("/entry/author[1]/uri", doc),
+ {:ok, _} <- Pleroma.Web.OStatus.make_user(uri, true),
+ {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
+ {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
+ {:ok, doc}
+ end
+ end
+ end
+
+ def salmon_incoming(conn, _) do
{:ok, body, _conn} = read_body(conn)
- {:ok, magic_key} = Pleroma.Web.Salmon.fetch_magic_key(body)
- {:ok, doc} = Pleroma.Web.Salmon.decode_and_validate(magic_key, body)
+ {:ok, doc} = decode_or_retry(body)
Federator.enqueue(:incoming_doc, doc)
@@ -69,6 +86,19 @@ defmodule Pleroma.Web.OStatus.OStatusController do
end
end
+ def notice(conn, %{"id" => id}) do
+ with %Activity{} = activity <- Repo.get(Activity, id),
+ %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
+ case get_format(conn) do
+ "html" ->
+ conn
+ |> put_resp_content_type("text/html")
+ |> send_file(200, "priv/static/index.html")
+ _ -> represent_activity(conn, activity, user)
+ end
+ end
+ end
+
defp represent_activity(conn, activity, user) do
response = activity
|> ActivityRepresenter.to_simple_form(user, true)
diff --git a/lib/pleroma/web/ostatus/user_representer.ex b/lib/pleroma/web/ostatus/user_representer.ex
index 14f78a4ed..20ebb3e08 100644
--- a/lib/pleroma/web/ostatus/user_representer.ex
+++ b/lib/pleroma/web/ostatus/user_representer.ex
@@ -19,6 +19,7 @@ defmodule Pleroma.Web.OStatus.UserRepresenter do
{:"poco:preferredUsername", [nickname]},
{:"poco:displayName", [name]},
{:"poco:note", [bio]},
+ {:summary, [bio]},
{:name, [nickname]},
{:link, [rel: 'avatar', href: avatar_url], []}
] ++ banner
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 6abf234c6..6806e8a75 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -21,6 +21,13 @@ defmodule Pleroma.Web.Router do
plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1}
end
+ pipeline :mastodon_html do
+ plug :accepts, ["html"]
+ plug :fetch_session
+ plug Pleroma.Plugs.OAuthPlug
+ plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true}
+ end
+
pipeline :well_known do
plug :accepts, ["xml", "xrd+xml"]
end
@@ -33,6 +40,17 @@ defmodule Pleroma.Web.Router do
plug :accepts, ["html", "json"]
end
+ pipeline :pleroma_api do
+ plug :accepts, ["html", "json"]
+ end
+
+ scope "/api/pleroma", Pleroma.Web.TwitterAPI do
+ pipe_through :pleroma_api
+ get "/password_reset/:token", UtilController, :show_password_reset
+ post "/password_reset", UtilController, :password_reset
+ get "/emoji", UtilController, :emoji
+ end
+
scope "/oauth", Pleroma.Web.OAuth do
get "/authorize", OAuthController, :authorize
post "/authorize", OAuthController, :create_authorization
@@ -42,16 +60,21 @@ defmodule Pleroma.Web.Router do
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through :authenticated_api
+ patch "/accounts/update_credentials", MastodonAPIController, :update_credentials
get "/accounts/verify_credentials", MastodonAPIController, :verify_credentials
get "/accounts/relationships", MastodonAPIController, :relationships
+ get "/accounts/search", MastodonAPIController, :account_search
post "/accounts/:id/follow", MastodonAPIController, :follow
post "/accounts/:id/unfollow", MastodonAPIController, :unfollow
- post "/accounts/:id/block", MastodonAPIController, :relationship_noop
- post "/accounts/:id/unblock", MastodonAPIController, :relationship_noop
+ post "/accounts/:id/block", MastodonAPIController, :block
+ post "/accounts/:id/unblock", MastodonAPIController, :unblock
post "/accounts/:id/mute", MastodonAPIController, :relationship_noop
post "/accounts/:id/unmute", MastodonAPIController, :relationship_noop
- get "/blocks", MastodonAPIController, :empty_array
+ post "/follows", MastodonAPIController, :follow
+
+ get "/blocks", MastodonAPIController, :blocks
+
get "/domain_blocks", MastodonAPIController, :empty_array
get "/follow_requests", MastodonAPIController, :empty_array
get "/mutes", MastodonAPIController, :empty_array
@@ -67,7 +90,10 @@ defmodule Pleroma.Web.Router do
post "/statuses/:id/favourite", MastodonAPIController, :fav_status
post "/statuses/:id/unfavourite", MastodonAPIController, :unfav_status
+ post "/notifications/clear", MastodonAPIController, :clear_notifications
+ post "/notifications/dismiss", MastodonAPIController, :dismiss_notification
get "/notifications", MastodonAPIController, :notifications
+ get "/notifications/:id", MastodonAPIController, :get_notification
post "/media", MastodonAPIController, :upload
end
@@ -76,6 +102,7 @@ defmodule Pleroma.Web.Router do
pipe_through :api
get "/instance", MastodonAPIController, :masto_instance
post "/apps", MastodonAPIController, :create_app
+ get "/custom_emojis", MastodonAPIController, :custom_emojis
get "/timelines/public", MastodonAPIController, :public_timeline
get "/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline
@@ -113,6 +140,7 @@ defmodule Pleroma.Web.Router do
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/show/:id", TwitterAPI.Controller, :fetch_status
get "/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation
@@ -123,7 +151,6 @@ defmodule Pleroma.Web.Router do
get "/search", TwitterAPI.Controller, :search
get "/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline
- get "/externalprofile/show", TwitterAPI.Controller, :external_profile
end
scope "/api", Pleroma.Web do
@@ -149,6 +176,8 @@ defmodule Pleroma.Web.Router do
post "/friendships/create", TwitterAPI.Controller, :follow
post "/friendships/destroy", TwitterAPI.Controller, :unfollow
+ post "/blocks/create", TwitterAPI.Controller, :block
+ post "/blocks/destroy", TwitterAPI.Controller, :unblock
post "/statusnet/media/upload", TwitterAPI.Controller, :upload
post "/media/upload", TwitterAPI.Controller, :upload_json
@@ -161,6 +190,12 @@ defmodule Pleroma.Web.Router do
get "/statuses/followers", TwitterAPI.Controller, :followers
get "/statuses/friends", TwitterAPI.Controller, :friends
+ get "/friends/ids", TwitterAPI.Controller, :friends_ids
+ get "/friendships/no_retweets/ids", TwitterAPI.Controller, :empty_array
+
+ get "/mutes/users/ids", TwitterAPI.Controller, :empty_array
+
+ get "/externalprofile/show", TwitterAPI.Controller, :external_profile
end
pipeline :ostatus do
@@ -172,6 +207,7 @@ defmodule Pleroma.Web.Router do
get "/objects/:uuid", OStatus.OStatusController, :object
get "/activities/:uuid", OStatus.OStatusController, :activity
+ get "/notice/:id", OStatus.OStatusController, :notice
get "/users/:nickname/feed", OStatus.OStatusController, :feed
get "/users/:nickname", OStatus.OStatusController, :feed_redirect
@@ -188,6 +224,15 @@ defmodule Pleroma.Web.Router do
get "/webfinger", WebFinger.WebFingerController, :webfinger
end
+ scope "/", Pleroma.Web.MastodonAPI do
+ pipe_through :mastodon_html
+
+ get "/web/login", MastodonAPIController, :login
+ post "/web/login", MastodonAPIController, :login_post
+ get "/web/*path", MastodonAPIController, :index
+ delete "/auth/sign_out", MastodonAPIController, :logout
+ end
+
scope "/", Fallback do
get "/*path", RedirectController, :redirector
end
diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex
index 4f6dfed65..81b864582 100644
--- a/lib/pleroma/web/salmon/salmon.ex
+++ b/lib/pleroma/web/salmon/salmon.ex
@@ -73,17 +73,30 @@ defmodule Pleroma.Web.Salmon do
"RSA.#{modulus_enc}.#{exponent_enc}"
end
- def generate_rsa_pem do
- port = Port.open({:spawn, "openssl genrsa"}, [:binary])
- {:ok, pem} = receive do
- {^port, {:data, pem}} -> {:ok, pem}
- end
- Port.close(port)
- if Regex.match?(~r/RSA PRIVATE KEY/, pem) do
+ # Native generation of RSA keys is only available since OTP 20+ and in default build conditions
+ # We try at compile time to generate natively an RSA key otherwise we fallback on the old way.
+ try do
+ _ = :public_key.generate_key({:rsa, 2048, 65537})
+ def generate_rsa_pem do
+ key = :public_key.generate_key({:rsa, 2048, 65537})
+ entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
+ pem = :public_key.pem_encode([entry]) |> String.trim_trailing
{:ok, pem}
- else
- :error
end
+ rescue
+ _ ->
+ def generate_rsa_pem do
+ port = Port.open({:spawn, "openssl genrsa"}, [:binary])
+ {:ok, pem} = receive do
+ {^port, {:data, pem}} -> {:ok, pem}
+ end
+ Port.close(port)
+ if Regex.match?(~r/RSA PRIVATE KEY/, pem) do
+ {:ok, pem}
+ else
+ :error
+ end
+ end
end
def keys_from_pem(pem) do
diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex
new file mode 100644
index 000000000..d64e6c393
--- /dev/null
+++ b/lib/pleroma/web/streamer.ex
@@ -0,0 +1,112 @@
+defmodule Pleroma.Web.Streamer do
+ use GenServer
+ require Logger
+ alias Pleroma.{User, Notification}
+
+ def start_link do
+ spawn(fn ->
+ Process.sleep(1000 * 30) # 30 seconds
+ GenServer.cast(__MODULE__, %{action: :ping})
+ end)
+ GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
+ end
+
+ def add_socket(topic, socket) do
+ GenServer.cast(__MODULE__, %{action: :add, socket: socket, topic: topic})
+ end
+
+ def remove_socket(topic, socket) do
+ GenServer.cast(__MODULE__, %{action: :remove, socket: socket, topic: topic})
+ end
+
+ def stream(topic, item) do
+ GenServer.cast(__MODULE__, %{action: :stream, topic: topic, item: item})
+ end
+
+ def handle_cast(%{action: :ping}, topics) do
+ Map.values(topics)
+ |> List.flatten
+ |> Enum.each(fn (socket) ->
+ Logger.debug("Sending keepalive ping")
+ send socket.transport_pid, {:text, ""}
+ end)
+ spawn(fn ->
+ Process.sleep(1000 * 30) # 30 seconds
+ GenServer.cast(__MODULE__, %{action: :ping})
+ end)
+ {:noreply, topics}
+ end
+
+ def handle_cast(%{action: :stream, topic: "user", item: %Notification{} = item}, topics) do
+ topic = "user:#{item.user_id}"
+ Enum.each(topics[topic] || [], fn (socket) ->
+ json = %{
+ event: "notification",
+ payload: Pleroma.Web.MastodonAPI.MastodonAPIController.render_notification(socket.assigns["user"], item) |> Poison.encode!
+ } |> Poison.encode!
+
+ send socket.transport_pid, {:text, json}
+ end)
+ {:noreply, topics}
+ end
+
+ def handle_cast(%{action: :stream, topic: "user", item: item}, topics) do
+ Logger.debug("Trying to push to users")
+ recipient_topics = User.get_recipients_from_activity(item)
+ |> Enum.map(fn (%{id: id}) -> "user:#{id}" end)
+
+ Enum.each(recipient_topics, fn (topic) ->
+ push_to_socket(topics, topic, item)
+ end)
+ {:noreply, topics}
+ end
+
+ def handle_cast(%{action: :stream, topic: topic, item: item}, topics) do
+ Logger.debug("Trying to push to #{topic}")
+ Logger.debug("Pushing item to #{topic}")
+ push_to_socket(topics, topic, item)
+ {:noreply, topics}
+ end
+
+ def handle_cast(%{action: :add, topic: topic, socket: socket}, sockets) do
+ topic = internal_topic(topic, socket)
+ sockets_for_topic = sockets[topic] || []
+ sockets_for_topic = Enum.uniq([socket | sockets_for_topic])
+ sockets = Map.put(sockets, topic, sockets_for_topic)
+ Logger.debug("Got new conn for #{topic}")
+ IO.inspect(sockets)
+ {:noreply, sockets}
+ end
+
+ def handle_cast(%{action: :remove, topic: topic, socket: socket}, sockets) do
+ topic = internal_topic(topic, socket)
+ sockets_for_topic = sockets[topic] || []
+ sockets_for_topic = List.delete(sockets_for_topic, socket)
+ sockets = Map.put(sockets, topic, sockets_for_topic)
+ Logger.debug("Removed conn for #{topic}")
+ IO.inspect(sockets)
+ {:noreply, sockets}
+ end
+
+ def handle_cast(m, state) do
+ IO.inspect("Unknown: #{inspect(m)}, #{inspect(state)}")
+ {:noreply, state}
+ end
+
+ def push_to_socket(topics, topic, item) do
+ Enum.each(topics[topic] || [], fn (socket) ->
+ json = %{
+ event: "update",
+ payload: Pleroma.Web.MastodonAPI.StatusView.render("status.json", activity: item, for: socket.assigns[:user]) |> Poison.encode!
+ } |> Poison.encode!
+
+ send socket.transport_pid, {:text, json}
+ end)
+ end
+
+ defp internal_topic("user", socket) do
+ "user:#{socket.assigns[:user].id}"
+ end
+
+ defp internal_topic(topic, _), do: topic
+end
diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex
index 6cc3b7ac5..2a8dede80 100644
--- a/lib/pleroma/web/templates/layout/app.html.eex
+++ b/lib/pleroma/web/templates/layout/app.html.eex
@@ -3,9 +3,73 @@
<head>
<meta charset=utf-8 />
<title>Pleroma</title>
+ <style>
+ body {
+ background-color: #282c37;
+ font-family: sans-serif;
+ color:white;
+ text-align: center;
+ }
+
+ .container {
+ margin: 50px auto;
+ max-width: 320px;
+ padding: 0;
+ padding: 40px 40px 40px 40px;
+ background-color: #313543;
+ border-radius: 4px;
+ }
+
+ h1 {
+ margin: 0;
+ }
+
+ h2 {
+ color: #9baec8;
+ font-weight: normal;
+ font-size: 20px;
+ margin-bottom: 40px;
+ }
+
+ form {
+ width: 100%;
+ }
+
+ input {
+ box-sizing: border-box;
+ width: 100%;
+ padding: 10px;
+ margin-top: 20px;
+ background-color: rgba(0,0,0,.1);
+ color: white;
+ border: 0;
+ border-bottom: 2px solid #9baec8;
+ font-size: 14px;
+ }
+
+ input:focus {
+ border-bottom: 2px solid #4b8ed8;
+ }
+
+ button {
+ box-sizing: border-box;
+ width: 100%;
+ color: white;
+ background-color: #419bdd;
+ border-radius: 4px;
+ border: none;
+ padding: 10px;
+ margin-top: 30px;
+ text-transform: uppercase;
+ font-weight: 500;
+ font-size: 16px;
+ }
+ </style>
</head>
<body>
- <h1>Welcome to Pleroma</h1>
- <%= render @view_module, @view_template, assigns %>
+ <div class="container">
+ <h1>Pleroma</h1>
+ <%= render @view_module, @view_template, assigns %>
+ </div>
</body>
</html>
diff --git a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex
new file mode 100644
index 000000000..ac50ad46b
--- /dev/null
+++ b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<meta charset='utf-8'>
+<meta content='width=device-width, initial-scale=1' name='viewport'>
+<link rel="stylesheet" media="all" href="/packs/common.css" />
+<link rel="stylesheet" media="all" href="/packs/default.css" />
+<link rel="stylesheet" media="all" href="/packs/pl-dark-masto-fe.css" />
+
+<script src="/packs/common.js"></script>
+<script src="/packs/locale_en.js"></script>
+<script id='initial-state' type='application/json'><%= raw @initial_state %></script>
+<script src="/packs/application.js"></script>
+</head>
+<body class='app-body'>
+ <div class='app-holder' data-props='{&quot;locale&quot;:&quot;en&quot;}' id='mastodon'>
+ </div>
+</body>
+</html>
diff --git a/lib/pleroma/web/templates/mastodon_api/mastodon/login.html.eex b/lib/pleroma/web/templates/mastodon_api/mastodon/login.html.eex
new file mode 100644
index 000000000..2ef67b901
--- /dev/null
+++ b/lib/pleroma/web/templates/mastodon_api/mastodon/login.html.eex
@@ -0,0 +1,11 @@
+<h2>Login in to Mastodon Frontend</h2>
+<%= if @error do %>
+ <h2><%= @error %></h2>
+<% end %>
+<%= form_for @conn, mastodon_api_path(@conn, :login), [as: "authorization"], fn f -> %>
+<%= text_input f, :name, placeholder: "Username" %>
+<br>
+<%= password_input f, :password, placeholder: "Password" %>
+<br>
+<%= submit "Log in" %>
+<% end %>
diff --git a/lib/pleroma/web/templates/twitter_api/util/invalid_token.html.eex b/lib/pleroma/web/templates/twitter_api/util/invalid_token.html.eex
new file mode 100644
index 000000000..ee84750c7
--- /dev/null
+++ b/lib/pleroma/web/templates/twitter_api/util/invalid_token.html.eex
@@ -0,0 +1 @@
+<h2>Invalid Token</h2>
diff --git a/lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex b/lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex
new file mode 100644
index 000000000..3c7960998
--- /dev/null
+++ b/lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex
@@ -0,0 +1,12 @@
+<h2>Password Reset for <%= @user.nickname %></h2>
+<%= form_for @conn, util_path(@conn, :password_reset), [as: "data"], fn f -> %>
+<%= label f, :password, "Password" %>
+<%= password_input f, :password %>
+<br>
+
+<%= label f, :password_confirmation, "Confirmation" %>
+<%= password_input f, :password_confirmation %>
+<br>
+<%= hidden_input f, :token, value: @token.token %>
+<%= submit "Reset" %>
+<% end %>
diff --git a/lib/pleroma/web/templates/twitter_api/util/password_reset_failed.html.eex b/lib/pleroma/web/templates/twitter_api/util/password_reset_failed.html.eex
new file mode 100644
index 000000000..58a3736fd
--- /dev/null
+++ b/lib/pleroma/web/templates/twitter_api/util/password_reset_failed.html.eex
@@ -0,0 +1 @@
+<h2>Password reset failed</h2>
diff --git a/lib/pleroma/web/templates/twitter_api/util/password_reset_success.html.eex b/lib/pleroma/web/templates/twitter_api/util/password_reset_success.html.eex
new file mode 100644
index 000000000..c7dfcb6dd
--- /dev/null
+++ b/lib/pleroma/web/templates/twitter_api/util/password_reset_success.html.eex
@@ -0,0 +1 @@
+<h2>Password changed!</h2>
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index 32910d92c..de2abd4d1 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -1,6 +1,29 @@
defmodule Pleroma.Web.TwitterAPI.UtilController do
use Pleroma.Web, :controller
alias Pleroma.Web
+ alias Pleroma.Formatter
+
+ alias Pleroma.{Repo, PasswordResetToken, User}
+
+ def show_password_reset(conn, %{"token" => token}) do
+ with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
+ %User{} = user <- Repo.get(User, token.user_id) do
+ render conn, "password_reset.html", %{
+ token: token,
+ user: user
+ }
+ else
+ _e -> render conn, "invalid_token.html"
+ end
+ end
+
+ def password_reset(conn, %{"data" => data}) do
+ with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do
+ render conn, "password_reset_success.html"
+ else
+ _e -> render conn, "password_reset_failed.html"
+ end
+ end
def help_test(conn, _params) do
json(conn, "ok")
@@ -46,4 +69,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
_ -> json(conn, version)
end
end
+
+ def emoji(conn, _params) do
+ json conn, Enum.into(Formatter.get_custom_emoji(), %{})
+ end
end
diff --git a/lib/pleroma/web/twitter_api/representers/activity_representer.ex b/lib/pleroma/web/twitter_api/representers/activity_representer.ex
index 929e26bf0..1f11bc9ac 100644
--- a/lib/pleroma/web/twitter_api/representers/activity_representer.ex
+++ b/lib/pleroma/web/twitter_api/representers/activity_representer.ex
@@ -97,7 +97,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
}
end
- def to_map(%Activity{data: %{"type" => "Delete", "published" => created_at, "object" => deleted_object }} = activity, %{user: user} = opts) do
+ def to_map(%Activity{data: %{"type" => "Delete", "published" => created_at, "object" => _ }} = activity, %{user: user} = opts) do
created_at = created_at |> Utils.date_to_asctime
%{
@@ -135,6 +135,13 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
tags = activity.data["object"]["tag"] || []
possibly_sensitive = Enum.member?(tags, "nsfw")
+ summary = activity.data["object"]["summary"]
+ content = if !!summary and summary != "" do
+ "<span>#{activity.data["object"]["summary"]}</span><br />#{content}</span>"
+ else
+ content
+ end
+
html = HtmlSanitizeEx.basic_html(content) |> Formatter.emojify(object["emoji"])
%{
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index d5c5cf5cf..d04a81cd4 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -4,27 +4,29 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter
alias Pleroma.Web.TwitterAPI.UserView
alias Pleroma.Web.{OStatus, CommonAPI}
- alias Pleroma.Formatter
import Ecto.Query
@httpoison Application.get_env(:pleroma, :httpoison)
- def create_status(%User{} = user, %{"status" => status} = data) do
+ def create_status(%User{} = user, %{"status" => _} = data) do
CommonAPI.post(user, data)
end
def fetch_friend_statuses(user, opts \\ %{}) do
+ opts = Map.put(opts, "blocking_user", user)
ActivityPub.fetch_activities([user.ap_id | user.following], opts)
|> activities_to_statuses(%{for: user})
end
def fetch_public_statuses(user, opts \\ %{}) do
opts = Map.put(opts, "local_only", true)
+ opts = Map.put(opts, "blocking_user", user)
ActivityPub.fetch_public_activities(opts)
|> activities_to_statuses(%{for: user})
end
def fetch_public_and_external_statuses(user, opts \\ %{}) do
+ opts = Map.put(opts, "blocking_user", user)
ActivityPub.fetch_public_activities(opts)
|> activities_to_statuses(%{for: user})
end
@@ -41,7 +43,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
def fetch_conversation(user, id) do
with context when is_binary(context) <- conversation_id_to_context(id),
- activities <- ActivityPub.fetch_activities_for_context(context),
+ activities <- ActivityPub.fetch_activities_for_context(context, %{"blocking_user" => user}),
statuses <- activities |> activities_to_statuses(%{for: user})
do
statuses
@@ -83,6 +85,26 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
end
end
+ def block(%User{} = blocker, params) do
+ with {:ok, %User{} = blocked} <- get_user(params),
+ {:ok, blocker} <- User.block(blocker, blocked)
+ do
+ {:ok, blocker, blocked}
+ else
+ err -> err
+ end
+ end
+
+ def unblock(%User{} = blocker, params) do
+ with {:ok, %User{} = blocked} <- get_user(params),
+ {:ok, blocker} <- User.unblock(blocker, blocked)
+ do
+ {:ok, blocker, blocked}
+ else
+ err -> err
+ end
+ end
+
def repeat(%User{} = user, ap_id_or_id) do
with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(ap_id_or_id, user),
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id),
@@ -193,7 +215,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
end
end
- defp parse_int(string, default \\ nil)
+ defp parse_int(string, default)
defp parse_int(string, default) when is_binary(string) do
with {n, _} <- Integer.parse(string) do
n
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
index 6154d5ad7..73d96c73d 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -3,17 +3,18 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView}
alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter
alias Pleroma.Web.CommonAPI
- alias Pleroma.{Repo, Activity, User, Object}
+ alias Pleroma.{Repo, Activity, User}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Ecto.Changeset
require Logger
def verify_credentials(%{assigns: %{user: user}} = conn, _params) do
- render(conn, UserView, "show.json", %{user: user})
+ token = Phoenix.Token.sign(conn, "user socket", user.id)
+ render(conn, UserView, "show.json", %{user: user, token: token})
end
- def status_update(%{assigns: %{user: user}} = conn, %{"status" => status_text} = status_data) do
+ def status_update(%{assigns: %{user: user}} = conn, %{"status" => _} = status_data) do
with media_ids <- extract_media_ids(status_data),
{:ok, activity} <- TwitterAPI.create_status(user, Map.put(status_data, "media_ids", media_ids)) do
conn
@@ -65,10 +66,23 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|> json_reply(200, json)
end
+ def show_user(conn, params) do
+ with {:ok, shown} <- TwitterAPI.get_user(params) do
+ if user = conn.assigns.user do
+ render conn, UserView, "show.json", %{user: shown, for: user}
+ else
+ render conn, UserView, "show.json", %{user: shown}
+ end
+ else
+ {:error, msg} ->
+ bad_request_reply(conn, msg)
+ end
+ end
+
def user_timeline(%{assigns: %{user: user}} = conn, params) do
case TwitterAPI.get_user(user, params) do
{:ok, target_user} ->
- params = Map.merge(params, %{"actor_id" => target_user.ap_id})
+ params = Map.merge(params, %{"actor_id" => target_user.ap_id, "whole_db" => true})
statuses = TwitterAPI.fetch_user_statuses(user, params)
conn
|> json_reply(200, statuses |> Poison.encode!)
@@ -93,6 +107,22 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
end
end
+ def block(%{assigns: %{user: user}} = conn, params) do
+ case TwitterAPI.block(user, params) do
+ {:ok, user, blocked} ->
+ render conn, UserView, "show.json", %{user: blocked, for: user}
+ {:error, msg} -> forbidden_json_reply(conn, msg)
+ end
+ end
+
+ def unblock(%{assigns: %{user: user}} = conn, params) do
+ case TwitterAPI.unblock(user, params) do
+ {:ok, user, blocked} ->
+ render conn, UserView, "show.json", %{user: blocked, for: user}
+ {:error, msg} -> forbidden_json_reply(conn, msg)
+ end
+ end
+
def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, delete} <- CommonAPI.delete(id, user) do
json = ActivityRepresenter.to_json(delete, %{user: user, for: user})
@@ -186,8 +216,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}),
new_info <- Map.put(user.info, "banner", object.data),
change <- User.info_changeset(user, %{info: new_info}),
- {:ok, user} <- Repo.update(change) do
- %{"url" => [ %{ "href" => href } | t ]} = object.data
+ {:ok, _user} <- Repo.update(change) do
+ %{"url" => [ %{ "href" => href } | _ ]} = object.data
response = %{ url: href } |> Poison.encode!
conn
|> json_reply(200, response)
@@ -198,8 +228,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
with {:ok, object} <- ActivityPub.upload(params),
new_info <- Map.put(user.info, "background", object.data),
change <- User.info_changeset(user, %{info: new_info}),
- {:ok, user} <- Repo.update(change) do
- %{"url" => [ %{ "href" => href } | t ]} = object.data
+ {:ok, _user} <- Repo.update(change) do
+ %{"url" => [ %{ "href" => href } | _ ]} = object.data
response = %{ url: href } |> Poison.encode!
conn
|> json_reply(200, response)
@@ -225,7 +255,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
mrn <- max(id, user.info["most_recent_notification"] || 0),
updated_info <- Map.put(info, "most_recent_notification", mrn),
changeset <- User.info_changeset(user, %{info: updated_info}),
- {:ok, user} <- Repo.update(changeset) do
+ {:ok, _user} <- Repo.update(changeset) do
conn
|> json_reply(200, Poison.encode!(mrn))
else
@@ -249,6 +279,22 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
end
end
+ def friends_ids(%{assigns: %{user: user}} = conn, _params) do
+ with {:ok, friends} <- User.get_friends(user) do
+ ids = friends
+ |> Enum.map(fn x -> x.id end)
+ |> Poison.encode!
+
+ json(conn, ids)
+ else
+ _e -> bad_request_reply(conn, "Can't get friends")
+ end
+ end
+
+ def empty_array(conn, _params) do
+ json(conn, Poison.encode!([]))
+ end
+
def update_profile(%{assigns: %{user: user}} = conn, params) do
params = if bio = params["description"] do
Map.put(params, "bio", bio)
@@ -266,7 +312,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
end
end
- def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
+ def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do
conn
|> json(TwitterAPI.search(user, params))
end
diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex
index f72e951eb..d1c7e6fbd 100644
--- a/lib/pleroma/web/twitter_api/views/user_view.ex
+++ b/lib/pleroma/web/twitter_api/views/user_view.ex
@@ -11,25 +11,28 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
render_many(users, Pleroma.Web.TwitterAPI.UserView, "user.json", for: user)
end
- defp image_url(%{"url" => [ %{ "href" => href } | t ]}), do: href
- defp image_url(_), do: nil
-
def render("user.json", %{user: user = %User{}} = assigns) do
image = User.avatar_url(user)
- following = if assigns[:for] do
- User.following?(assigns[:for], user)
+ {following, follows_you, statusnet_blocking} = if assigns[:for] do
+ {
+ User.following?(assigns[:for], user),
+ User.following?(user, assigns[:for]),
+ User.blocks?(assigns[:for], user)
+ }
else
- false
+ {false, false, false}
end
user_info = User.get_cached_user_info(user)
- %{
+ data = %{
"created_at" => user.inserted_at |> Utils.format_naive_asctime,
"description" => HtmlSanitizeEx.strip_tags(user.bio),
"favourites_count" => 0,
"followers_count" => user_info[:follower_count],
"following" => following,
+ "follows_you" => follows_you,
+ "statusnet_blocking" => statusnet_blocking,
"friends_count" => user_info[:following_count],
"id" => user.id,
"name" => user.name,
@@ -44,6 +47,12 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
"cover_photo" => image_url(user.info["banner"]),
"background_image" => image_url(user.info["background"])
}
+
+ if assigns[:token] do
+ Map.put(data, "token", assigns[:token])
+ else
+ data
+ end
end
def render("short.json", %{user: %User{
@@ -57,4 +66,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
"screen_name" => nickname
}
end
+
+ defp image_url(%{"url" => [ %{ "href" => href } | _ ]}), do: href
+ defp image_url(_), do: nil
end
diff --git a/lib/pleroma/web/twitter_api/views/util_view.ex b/lib/pleroma/web/twitter_api/views/util_view.ex
new file mode 100644
index 000000000..71b04e6cc
--- /dev/null
+++ b/lib/pleroma/web/twitter_api/views/util_view.ex
@@ -0,0 +1,4 @@
+defmodule Pleroma.Web.TwitterAPI.UtilView do
+ use Pleroma.Web, :view
+ import Phoenix.HTML.Form
+end
diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex
index 7cbafe11f..026d2f98b 100644
--- a/lib/pleroma/web/web_finger/web_finger.ex
+++ b/lib/pleroma/web/web_finger/web_finger.ex
@@ -89,7 +89,7 @@ defmodule Pleroma.Web.WebFinger do
with {:ok, %{status_code: status_code, body: body}} when status_code in 200..299 <- @httpoison.get("http://#{domain}/.well-known/host-meta", [], follow_redirect: true) do
get_template_from_xml(body)
else
- e ->
+ _ ->
with {:ok, %{body: body}} <- @httpoison.get("https://#{domain}/.well-known/host-meta", []) do
get_template_from_xml(body)
else
diff --git a/lib/pleroma/web/websub/websub.ex b/lib/pleroma/web/websub/websub.ex
index 6bbf13130..db1577a93 100644
--- a/lib/pleroma/web/websub/websub.ex
+++ b/lib/pleroma/web/websub/websub.ex
@@ -31,9 +31,9 @@ defmodule Pleroma.Web.Websub do
do
changeset = Changeset.change(subscription, %{state: "active"})
Repo.update(changeset)
- else _e ->
- changeset = Changeset.change(subscription, %{state: "rejected"})
- {:ok, subscription} = Repo.update(changeset)
+ else e ->
+ Logger.debug("Couldn't verify subscription")
+ Logger.debug(inspect(e))
{:error, subscription}
end
end
diff --git a/lib/pleroma/web/xml/xml.ex b/lib/pleroma/web/xml/xml.ex
index 63580c1f8..026672ad1 100644
--- a/lib/pleroma/web/xml/xml.ex
+++ b/lib/pleroma/web/xml/xml.ex
@@ -1,7 +1,7 @@
defmodule Pleroma.Web.XML do
require Logger
- def string_from_xpath(xpath, :error), do: nil
+ def string_from_xpath(_, :error), do: nil
def string_from_xpath(xpath, doc) do
{:xmlObj, :string, res} = :xmerl_xpath.string('string(#{xpath})', doc)
@@ -20,7 +20,7 @@ defmodule Pleroma.Web.XML do
doc
catch
- :exit, error ->
+ :exit, _error ->
Logger.debug("Couldn't parse xml: #{inspect(text)}")
:error
end
diff --git a/lib/transports.ex b/lib/transports.ex
new file mode 100644
index 000000000..a820aa778
--- /dev/null
+++ b/lib/transports.ex
@@ -0,0 +1,77 @@
+defmodule Phoenix.Transports.WebSocket.Raw do
+ import Plug.Conn, only: [
+ fetch_query_params: 1,
+ send_resp: 3
+ ]
+ alias Phoenix.Socket.Transport
+
+ def default_config do
+ [
+ timeout: 60_000,
+ transport_log: false,
+ cowboy: Phoenix.Endpoint.CowboyWebSocket
+ ]
+ end
+
+ def init(%Plug.Conn{method: "GET"} = conn, {endpoint, handler, transport}) do
+ {_, opts} = handler.__transport__(transport)
+
+ conn = conn
+ |> fetch_query_params
+ |> Transport.transport_log(opts[:transport_log])
+ |> Transport.force_ssl(handler, endpoint, opts)
+ |> Transport.check_origin(handler, endpoint, opts)
+
+ case conn do
+ %{halted: false} = conn ->
+ case Transport.connect(endpoint, handler, transport, __MODULE__, nil, conn.params) do
+ {:ok, socket} ->
+ {:ok, conn, {__MODULE__, {socket, opts}}}
+ :error ->
+ send_resp(conn, :forbidden, "")
+ {:error, conn}
+ end
+ _ ->
+ {:error, conn}
+ end
+ end
+
+ def init(conn, _) do
+ send_resp(conn, :bad_request, "")
+ {:error, conn}
+ end
+
+ def ws_init({socket, config}) do
+ Process.flag(:trap_exit, true)
+ {:ok, %{socket: socket}, config[:timeout]}
+ end
+
+ def ws_handle(op, data, state) do
+ state.socket.handler
+ |> apply(:handle, [op, data, state])
+ |> case do
+ {op, data} ->
+ {:reply, {op, data}, state}
+ {op, data, state} ->
+ {:reply, {op, data}, state}
+ %{} = state ->
+ {:ok, state}
+ _ ->
+ {:ok, state}
+ end
+ end
+
+ def ws_info({_,_} = tuple, state) do
+ {:reply, tuple, state}
+ end
+
+ def ws_info(_tuple, state), do: {:ok, state}
+
+ def ws_close(state) do
+ ws_handle(:closed, :normal, state)
+ end
+
+ def ws_terminate(reason, state) do
+ ws_handle(:closed, reason, state)
+ end
+end
diff --git a/lib/xml_builder.ex b/lib/xml_builder.ex
index c6d144903..52358c437 100644
--- a/lib/xml_builder.ex
+++ b/lib/xml_builder.ex
@@ -37,6 +37,6 @@ defmodule Pleroma.XmlBuilder do
"#{attribute}=\"#{value}\""
end |> Enum.join(" ")
- [tag, attributes_string] |> Enum.join(" ") |> String.strip
+ [tag, attributes_string] |> Enum.join(" ") |> String.trim
end
end