diff options
Diffstat (limited to 'lib')
30 files changed, 402 insertions, 135 deletions
diff --git a/lib/mix/tasks/generate_config.ex b/lib/mix/tasks/generate_config.ex index ae9ac3b75..70a110561 100644 --- a/lib/mix/tasks/generate_config.ex +++ b/lib/mix/tasks/generate_config.ex @@ -9,20 +9,6 @@ defmodule Mix.Tasks.GenerateConfig do name = IO.gets("What is the name of your instance? (e.g. Pleroma/Soykaf): ") |> String.trim() email = IO.gets("What's your admin email address: ") |> String.trim() - mediaproxy = - IO.gets("Do you want to activate the mediaproxy? (y/N): ") - |> String.trim() - |> String.downcase() - |> String.starts_with?("y") - - proxy_url = - if mediaproxy do - IO.gets("What is the mediaproxy's URL? (e.g. https://cache.example.com): ") - |> String.trim() - else - "https://cache.example.com" - end - secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64) dbpass = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64) @@ -35,8 +21,6 @@ defmodule Mix.Tasks.GenerateConfig do email: email, name: name, secret: secret, - mediaproxy: mediaproxy, - proxy_url: proxy_url, dbpass: dbpass ) diff --git a/lib/mix/tasks/register_user.ex b/lib/mix/tasks/register_user.ex index 48236439b..4bec6b9d9 100644 --- a/lib/mix/tasks/register_user.ex +++ b/lib/mix/tasks/register_user.ex @@ -5,7 +5,7 @@ defmodule Mix.Tasks.RegisterUser do @shortdoc "Register user" def run([name, nickname, email, bio, password]) do - ensure_started(Repo, []) + Mix.Task.run("app.start") params = %{ name: name, diff --git a/lib/mix/tasks/rm_user.ex b/lib/mix/tasks/rm_user.ex new file mode 100644 index 000000000..6a698f360 --- /dev/null +++ b/lib/mix/tasks/rm_user.ex @@ -0,0 +1,14 @@ +defmodule Mix.Tasks.RmUser do + use Mix.Task + import Mix.Ecto + alias Pleroma.{User, Repo} + + @shortdoc "Permanently delete a user" + def run([nickname]) do + Mix.Task.run("app.start") + + with %User{local: true} = user <- User.get_by_nickname(nickname) do + User.delete(user) + end + end +end diff --git a/lib/mix/tasks/sample_config.eex b/lib/mix/tasks/sample_config.eex index 9330fae2d..e37c864c0 100644 --- a/lib/mix/tasks/sample_config.eex +++ b/lib/mix/tasks/sample_config.eex @@ -11,9 +11,9 @@ config :pleroma, :instance, registrations_open: true config :pleroma, :media_proxy, - enabled: <%= mediaproxy %>, - redirect_on_failure: true, - base_url: "<%= proxy_url %>" + enabled: false, + redirect_on_failure: true + #base_url: "https://cache.pleroma.social" # Configure your database config :pleroma, Pleroma.Repo, diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 9396b1826..456416fbd 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -144,7 +144,7 @@ defmodule Pleroma.Formatter do @emoji end - @link_regex ~r/https?:\/\/[\w\.\/?=\-#%&@~\(\)]+[\w\/]/u + @link_regex ~r/https?:\/\/[\w\.\/?=\-#\+%&@~'\(\):]+[\w\/]/u def html_escape(text) do Regex.split(@link_regex, text, include_captures: true) @@ -168,7 +168,13 @@ defmodule Pleroma.Formatter do subs = subs ++ Enum.map(links, fn {uuid, url} -> - {uuid, "<a href='#{url}'>#{url}</a>"} + {:safe, link} = Phoenix.HTML.Link.link(url, to: url) + + link = + link + |> IO.iodata_to_binary() + + {uuid, link} end) {subs, uuid_text} @@ -189,7 +195,9 @@ defmodule Pleroma.Formatter do subs = subs ++ - Enum.map(mentions, fn {match, %User{ap_id: ap_id}, uuid} -> + Enum.map(mentions, fn {match, %User{ap_id: ap_id, info: info}, uuid} -> + ap_id = info["source_data"]["url"] || ap_id + short_match = String.split(match, "@") |> tl() |> hd() {uuid, "<span><a href='#{ap_id}'>@<span>#{short_match}</span></a></span>"} end) diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex index cfce4c05c..82e241f21 100644 --- a/lib/pleroma/gopher/server.ex +++ b/lib/pleroma/gopher/server.ex @@ -51,8 +51,9 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do def info(text) do text = String.replace(text, ~r/[\t\n]/, "") + String.split(text, "\r") - |> Enum.map(fn (text) -> + |> Enum.map(fn text -> "i#{text}\tfake\(NULL)\t0\r\n" end) |> Enum.join("") @@ -82,7 +83,12 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <> info("#{like_count} likes, #{announcement_count} repeats") <> - "\r\n" <> info(HtmlSanitizeEx.strip_tags(String.replace(activity.data["object"]["content"], "<br>", "\r"))) + "\r\n" <> + info( + HtmlSanitizeEx.strip_tags( + String.replace(activity.data["object"]["content"], "<br>", "\r") + ) + ) end) |> Enum.join("\r\n") end diff --git a/lib/pleroma/plugs/http_signature.ex b/lib/pleroma/plugs/http_signature.ex index af160f3ee..8b9ccdd2d 100644 --- a/lib/pleroma/plugs/http_signature.ex +++ b/lib/pleroma/plugs/http_signature.ex @@ -14,19 +14,26 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do def call(conn, opts) do user = conn.params["actor"] Logger.debug("Checking sig for #{user}") + [signature | _] = get_req_header(conn, "signature") - if get_req_header(conn, "signature") do - conn = - conn - |> put_req_header( - "(request-target)", - String.downcase("#{conn.method}") <> " #{conn.request_path}" - ) + cond do + signature && String.contains?(signature, user) -> + conn = + conn + |> put_req_header( + "(request-target)", + String.downcase("#{conn.method}") <> " #{conn.request_path}" + ) + + assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn)) - assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn)) - else - Logger.debug("No signature header!") - conn + signature -> + Logger.debug("Signature not from actor") + assign(conn, :valid_signature, false) + + true -> + Logger.debug("No signature header!") + conn end end end diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index 8a9dc612a..e5df94009 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -58,7 +58,7 @@ defmodule Pleroma.Upload do } end - defp upload_path do + def upload_path do settings = Application.get_env(:pleroma, Pleroma.Upload) Keyword.fetch!(settings, :uploads) end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index abeb169d9..c77fd6816 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -250,6 +250,13 @@ defmodule Pleroma.User do Repo.get_by(User, nickname: nickname) end + def get_by_nickname_or_email(nickname_or_email) do + case user = Repo.get_by(User, nickname: nickname_or_email) do + %User{} -> user + nil -> Repo.get_by(User, email: nickname_or_email) + end + end + def get_cached_user_info(user) do key = "user_info:#{user.id}" Cachex.get!(:user_cache, key, fallback: fn _ -> user_info(user) end) @@ -375,6 +382,9 @@ defmodule Pleroma.User do end def search(query, resolve) do + # strip the beginning @ off if there is a query + query = String.trim_leading(query, "@") + if resolve do User.get_or_fetch_by_nickname(query) end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 04b50c1cc..d071135c4 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -61,7 +61,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do additional ), {:ok, activity} <- insert(create_data, local), - :ok <- maybe_federate(activity) do + :ok <- maybe_federate(activity), + {:ok, actor} <- User.increase_note_count(actor) do {:ok, activity} end end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 00b9f74ff..2871a2544 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -146,7 +146,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do %{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = data ) do with %User{} = actor <- User.get_or_fetch_by_ap_id(actor), - {:ok, object} <- get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id), + {:ok, object} <- + get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id), {:ok, activity, object} <- ActivityPub.like(actor, object, id, false) do {:ok, activity} else @@ -158,7 +159,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do %{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = data ) do with %User{} = actor <- User.get_or_fetch_by_ap_id(actor), - {:ok, object} <- get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id), + {:ok, object} <- + get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id), {:ok, activity, object} <- ActivityPub.announce(actor, object, id, false) do {:ok, activity} else @@ -209,7 +211,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end with %User{} = actor <- User.get_or_fetch_by_ap_id(actor), - {:ok, object} <- get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id), + {:ok, object} <- + get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id), {:ok, activity} <- ActivityPub.delete(object, false) do {:ok, activity} else diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 16a1d3e97..7b2bf8fa7 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -45,6 +45,21 @@ defmodule Pleroma.Web.ActivityPub.Utils do "#{Web.base_url()}/#{type}/#{UUID.generate()}" end + def create_context(context) do + context = context || generate_id("contexts") + changeset = Object.context_mapping(context) + + case Repo.insert(changeset) do + {:ok, object} -> + object + + # This should be solved by an upsert, but it seems ecto + # has problems accessing the constraint inside the jsonb. + {:error, _} -> + Object.get_cached_by_ap_id(context) + end + end + @doc """ Enqueues an activity for federation if it's local """ @@ -67,13 +82,17 @@ defmodule Pleroma.Web.ActivityPub.Utils do also adds it to an included object """ def lazy_put_activity_defaults(map) do + %{data: %{"id" => context}, id: context_id} = create_context(map["context"]) + map = map |> Map.put_new_lazy("id", &generate_activity_id/0) |> Map.put_new_lazy("published", &make_date/0) + |> Map.put_new("context", context) + |> Map.put_new("context_id", context_id) if is_map(map["object"]) do - object = lazy_put_object_defaults(map["object"]) + object = lazy_put_object_defaults(map["object"], map) %{map | "object" => object} else map @@ -83,10 +102,12 @@ defmodule Pleroma.Web.ActivityPub.Utils do @doc """ Adds an id and published date if they aren't there. """ - def lazy_put_object_defaults(map) do + def lazy_put_object_defaults(map, activity \\ %{}) do map |> Map.put_new_lazy("id", &generate_object_id/0) |> Map.put_new_lazy("published", &make_date/0) + |> Map.put_new("context", activity["context"]) + |> Map.put_new("context_id", activity["context_id"]) end @doc """ @@ -154,7 +175,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do def update_element_in_object(property, element, object) do with new_data <- - object.data |> Map.put("#{property}_count", length(element)) + object.data + |> Map.put("#{property}_count", length(element)) |> Map.put("#{property}s", element), changeset <- Changeset.change(object, data: new_data), {:ok, object} <- Repo.update(changeset), diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 21225c3b7..2c4b591d4 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -103,7 +103,6 @@ defmodule Pleroma.Web.CommonAPI do additional: %{"cc" => cc} }) - 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 49c4ee1eb..3c092d524 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -61,6 +61,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do def make_content_html(status, mentions, attachments, tags, no_attachment_links \\ false) do status + |> String.replace("\r", "") |> format_input(mentions, tags) |> maybe_add_attachments(attachments, no_attachment_links) end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index fdf7fb72a..1a012c1b4 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -11,7 +11,7 @@ defmodule Pleroma.Web.Endpoint do # # You should set gzip to true if you are running phoenix.digest # when deploying your static files in production. - plug(Plug.Static, at: "/media", from: "uploads", gzip: false) + plug(Plug.Static, at: "/media", from: Pleroma.Upload.upload_path(), gzip: false) plug( Plug.Static, @@ -34,7 +34,8 @@ defmodule Pleroma.Web.Endpoint do Plug.Parsers, parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], - json_decoder: Jason + json_decoder: Jason, + length: Application.get_env(:pleroma, :instance) |> Keyword.get(:upload_limit) ) plug(Plug.MethodOverride) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 58bb5e03a..c84c226e8 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -102,13 +102,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end @instance Application.get_env(:pleroma, :instance) + @mastodon_api_level "2.3.3" def masto_instance(conn, _params) do response = %{ uri: Web.base_url(), title: Keyword.get(@instance, :name), description: "A Pleroma instance, an alternative fediverse server", - version: Keyword.get(@instance, :version), + version: "#{@mastodon_api_level} (compatible; #{Keyword.get(@instance, :version)})", email: Keyword.get(@instance, :email), urls: %{ streaming_api: String.replace(Web.base_url(), ["http", "https"], "wss") @@ -211,9 +212,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> Map.put("actor_id", ap_id) |> Map.put("whole_db", true) - activities = - ActivityPub.fetch_public_activities(params) - |> Enum.reverse() + if params["pinned"] == "true" do + # Since Pleroma has no "pinned" posts feature, we'll just set an empty list here + activities = [] + else + activities = + ActivityPub.fetch_public_activities(params) + |> Enum.reverse() + end conn |> add_link_headers(:user_statuses, activities, params["id"]) @@ -494,6 +500,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do if Regex.match?(~r/https?:/, query) do with {:ok, activities} <- OStatus.fetch_activity_from_url(query) do activities + |> Enum.filter(fn + %{data: %{"type" => "Create"}} -> true + _ -> false + end) else _e -> [] end @@ -503,22 +513,30 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do from( a in Activity, where: fragment("?->>'type' = 'Create'", a.data), + where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients, where: fragment( "to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)", a.data, ^query ), - limit: 20 + limit: 20, + order_by: [desc: :id] ) statuses = Repo.all(q) ++ fetched + tags = + String.split(query) + |> Enum.uniq() + |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end) + |> Enum.map(fn tag -> String.slice(tag, 1..-1) end) + res = %{ "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user), "statuses" => StatusView.render("index.json", activities: statuses, for: user, as: :activity), - "hashtags" => [] + "hashtags" => tags } json(conn, res) @@ -593,35 +611,37 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do "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 + settings: + Map.get(user.info, "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 + } + } }, - sounds: %{ - follow: true, - favourite: true, - reblog: true, - mention: true - } - } - }, push_subscription: nil, accounts: accounts, custom_emojis: mastodon_emoji, @@ -638,6 +658,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end + def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do + with new_info <- Map.put(user.info, "settings", settings), + change <- User.info_changeset(user, %{info: new_info}), + {:ok, _user} <- User.update_and_set_cache(change) do + conn + |> json(%{}) + else + e -> + conn + |> json(%{error: inspect(e)}) + end + end + def login(conn, _) do conn |> render(MastodonView, "login.html", %{error: false}) @@ -660,7 +693,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def login_post(conn, %{"authorization" => %{"name" => name, "password" => password}}) do - with %User{} = user <- User.get_cached_by_nickname(name), + with %User{} = user <- User.get_by_nickname_or_email(name), true <- Pbkdf2.checkpw(password, user.password_hash), {:ok, app} <- get_or_make_app(), {:ok, auth} <- Authorization.create_authorization(app, user), diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index bc5ae5da7..df360644a 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -125,7 +125,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do (activity.data["object"]["emoji"] || []) |> Enum.map(fn {name, url} -> name = HtmlSanitizeEx.strip_tags(name) - url = HtmlSanitizeEx.strip_tags(url) + + url = + HtmlSanitizeEx.strip_tags(url) + |> MediaProxy.url() + %{shortcode: name, url: url, static_url: url} end) diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 05f366611..11dc1806f 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -29,7 +29,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do "redirect_uri" => redirect_uri } = params }) do - with %User{} = user <- User.get_cached_by_nickname(name), + with %User{} = user <- User.get_by_nickname_or_email(name), true <- Pbkdf2.checkpw(password, user.password_hash), %App{} = app <- Repo.get_by(App, client_id: client_id), {:ok, auth} <- Authorization.create_authorization(app, user) do @@ -63,7 +63,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do client_secret: params["client_secret"] ), fixed_token = fix_padding(params["code"]), - %Authorization{} = auth <- Repo.get_by(Authorization, token: fixed_token, app_id: app.id), + %Authorization{} = auth <- + Repo.get_by(Authorization, token: fixed_token, app_id: app.id), {:ok, token} <- Token.exchange_token(app, auth) do response = %{ token_type: "Bearer", diff --git a/lib/pleroma/web/ostatus/activity_representer.ex b/lib/pleroma/web/ostatus/activity_representer.ex index 2f28c456e..921a89bd0 100644 --- a/lib/pleroma/web/ostatus/activity_representer.ex +++ b/lib/pleroma/web/ostatus/activity_representer.ex @@ -131,7 +131,8 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do h.(activity.data["object"]["content"] |> String.replace(~r/[\n\r]/, ""))}, {:published, h.(inserted_at)}, {:updated, h.(updated_at)}, - {:"ostatus:conversation", [ref: h.(activity.data["context"])], h.(activity.data["context"])}, + {:"ostatus:conversation", [ref: h.(activity.data["context"])], + h.(activity.data["context"])}, {:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []} ] ++ summary ++ @@ -162,7 +163,8 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do # For notes, federate the object id. {:id, h.(activity.data["object"])} ]}, - {:"ostatus:conversation", [ref: h.(activity.data["context"])], h.(activity.data["context"])}, + {:"ostatus:conversation", [ref: h.(activity.data["context"])], + h.(activity.data["context"])}, {:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []}, {:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []}, {:"thr:in-reply-to", [ref: to_charlist(activity.data["object"])], []} @@ -193,7 +195,8 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do {:content, [type: 'html'], ['RT #{retweeted_activity.data["object"]["content"]}']}, {:published, h.(inserted_at)}, {:updated, h.(updated_at)}, - {:"ostatus:conversation", [ref: h.(activity.data["context"])], h.(activity.data["context"])}, + {:"ostatus:conversation", [ref: h.(activity.data["context"])], + h.(activity.data["context"])}, {:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []}, {:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []}, {:"activity:object", retweeted_xml} diff --git a/lib/pleroma/web/ostatus/handlers/note_handler.ex b/lib/pleroma/web/ostatus/handlers/note_handler.ex index b012abd51..bd6e92238 100644 --- a/lib/pleroma/web/ostatus/handlers/note_handler.ex +++ b/lib/pleroma/web/ostatus/handlers/note_handler.ex @@ -138,19 +138,15 @@ defmodule Pleroma.Web.OStatus.NoteHandler do do: note |> Map.put("inReplyTo", inReplyTo), else: note ) do - res = - ActivityPub.create(%{ - to: to, - actor: actor, - context: context, - object: note, - published: date, - local: false, - additional: %{"cc" => cc} - }) - - User.increase_note_count(actor) - res + ActivityPub.create(%{ + to: to, + actor: actor, + context: context, + object: note, + published: date, + local: false, + additional: %{"cc" => cc} + }) else %Activity{} = activity -> {:ok, activity} e -> {:error, e} diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 5f27f3caa..8f63fdc70 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -100,6 +100,7 @@ defmodule Pleroma.Web.Router do get("/domain_blocks", MastodonAPIController, :empty_array) get("/follow_requests", MastodonAPIController, :empty_array) get("/mutes", MastodonAPIController, :empty_array) + get("/lists", MastodonAPIController, :empty_array) get("/timelines/home", MastodonAPIController, :home_timeline) @@ -120,6 +121,12 @@ defmodule Pleroma.Web.Router do post("/media", MastodonAPIController, :upload) end + scope "/api/web", Pleroma.Web.MastodonAPI do + pipe_through(:authenticated_api) + + put("/settings", MastodonAPIController, :put_settings) + end + scope "/api/v1", Pleroma.Web.MastodonAPI do pipe_through(:api) get("/instance", MastodonAPIController, :masto_instance) @@ -205,6 +212,7 @@ defmodule Pleroma.Web.Router do get("/statuses/friends_timeline", TwitterAPI.Controller, :friends_timeline) get("/statuses/mentions", TwitterAPI.Controller, :mentions_timeline) get("/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline) + get("/qvitter/statuses/notifications", TwitterAPI.Controller, :notifications) post("/statuses/update", TwitterAPI.Controller, :status_update) post("/statuses/retweet/:id", TwitterAPI.Controller, :retweet) diff --git a/lib/pleroma/web/templates/mastodon_api/mastodon/login.html.eex b/lib/pleroma/web/templates/mastodon_api/mastodon/login.html.eex index 2ef67b901..2bb54977e 100644 --- a/lib/pleroma/web/templates/mastodon_api/mastodon/login.html.eex +++ b/lib/pleroma/web/templates/mastodon_api/mastodon/login.html.eex @@ -3,7 +3,7 @@ <h2><%= @error %></h2> <% end %> <%= form_for @conn, mastodon_api_path(@conn, :login), [as: "authorization"], fn f -> %> -<%= text_input f, :name, placeholder: "Username" %> +<%= text_input f, :name, placeholder: "Username or email" %> <br> <%= password_input f, :password, placeholder: "Password" %> <br> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex index a7fa7523b..de2241ec9 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex @@ -2,7 +2,7 @@ <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p> <h2>OAuth Authorization</h2> <%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> -<%= label f, :name, "Name" %> +<%= label f, :name, "Name or email" %> <%= text_input f, :name %> <br> <%= label f, :password, "Password" %> diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 8f452c31c..c7b1a5b95 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -182,13 +182,13 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do def follow_import(%{assigns: %{user: user}} = conn, %{"list" => list}) do Task.start(fn -> String.split(list) - |> Enum.map(fn nick -> + |> Enum.map(fn account -> with %User{} = follower <- User.get_cached_by_ap_id(user.ap_id), - %User{} = followed <- User.get_or_fetch_by_nickname(nick), + %User{} = followed <- User.get_or_fetch(account), {:ok, follower} <- User.follow(follower, followed) do ActivityPub.follow(follower, followed) else - _e -> Logger.debug("follow_import: following #{nick} failed") + _e -> Logger.debug("follow_import: following #{account} failed") end end) end) diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 30362ef70..c12cd7f8a 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -193,6 +193,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do from( a in Activity, where: fragment("?->>'type' = 'Create'", a.data), + where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients, where: fragment( "to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)", @@ -212,6 +213,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do DateTime.utc_now() |> DateTime.to_iso8601() end + # DEPRECATED mostly, context objects are now created at insertion time. def context_to_conversation_id(context) do with %Object{id: id} <- Object.get_cached_by_ap_id(context) do id diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 157e4826b..6cf8682b8 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -1,8 +1,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller do use Pleroma.Web, :controller - alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView} + alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView, NotificationView} alias Pleroma.Web.CommonAPI - alias Pleroma.{Repo, Activity, User} + alias Pleroma.{Repo, Activity, User, Notification} alias Pleroma.Web.ActivityPub.ActivityPub alias Ecto.Changeset @@ -45,7 +45,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do def public_and_external_timeline(%{assigns: %{user: user}} = conn, params) do params = params - |> Map.put("type", ["Create", "Announce", "Follow"]) + |> Map.put("type", ["Create", "Announce"]) |> Map.put("blocking_user", user) activities = ActivityPub.fetch_public_activities(params) @@ -57,7 +57,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do def public_timeline(%{assigns: %{user: user}} = conn, params) do params = params - |> Map.put("type", ["Create", "Announce", "Follow"]) + |> Map.put("type", ["Create", "Announce"]) |> Map.put("local_only", true) |> Map.put("blocking_user", user) @@ -119,6 +119,13 @@ defmodule Pleroma.Web.TwitterAPI.Controller do |> render(ActivityView, "index.json", %{activities: activities, for: user}) end + def notifications(%{assigns: %{user: user}} = conn, params) do + notifications = Notification.for_user(user, params) + + conn + |> render(NotificationView, "notification.json", %{notifications: notifications, for: user}) + end + def follow(%{assigns: %{user: user}} = conn, params) do case TwitterAPI.follow(user, params) do {:ok, user, followed, _activity} -> diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex index 3e69af3e3..4e4433aed 100644 --- a/lib/pleroma/web/twitter_api/views/activity_view.ex +++ b/lib/pleroma/web/twitter_api/views/activity_view.ex @@ -7,9 +7,101 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.User + alias Pleroma.Repo alias Pleroma.Formatter + import Ecto.Query + + defp query_context_ids([]), do: [] + + defp query_context_ids(contexts) do + query = from(o in Object, where: fragment("(?)->>'id' = ANY(?)", o.data, ^contexts)) + + Repo.all(query) + end + + defp query_users([]), do: [] + + defp query_users(user_ids) do + query = from(user in User, where: user.ap_id in ^user_ids) + + Repo.all(query) + end + + defp collect_context_ids(activities) do + contexts = + activities + |> Enum.reject(& &1.data["context_id"]) + |> Enum.map(fn %{data: data} -> + data["context"] + end) + |> Enum.filter(& &1) + |> query_context_ids() + |> Enum.reduce(%{}, fn %{data: %{"id" => ap_id}, id: id}, acc -> + Map.put(acc, ap_id, id) + end) + end + + defp collect_users(activities) do + activities + |> Enum.map(fn activity -> + case activity.data do + data = %{"type" => "Follow"} -> + [data["actor"], data["object"]] + + data -> + [data["actor"]] + end ++ activity.recipients + end) + |> List.flatten() + |> Enum.uniq() + |> query_users() + |> Enum.reduce(%{}, fn user, acc -> + Map.put(acc, user.ap_id, user) + end) + end + + defp get_context_id(%{data: %{"context_id" => context_id}}, _) when not is_nil(context_id), + do: context_id + + defp get_context_id(%{data: %{"context" => nil}}, _), do: nil + + defp get_context_id(%{data: %{"context" => context}}, options) do + cond do + id = options[:context_ids][context] -> id + true -> TwitterAPI.context_to_conversation_id(context) + end + end + + defp get_context_id(_, _), do: nil + + defp get_user(ap_id, opts) do + cond do + user = opts[:users][ap_id] -> + user + + String.ends_with?(ap_id, "/followers") -> + nil + + ap_id == "https://www.w3.org/ns/activitystreams#Public" -> + nil + + true -> + User.get_cached_by_ap_id(ap_id) + end + end + def render("index.json", opts) do + context_ids = collect_context_ids(opts.activities) + users = collect_users(opts.activities) + + opts = + opts + |> Map.put(:context_ids, context_ids) + |> Map.put(:users, users) + render_many( opts.activities, ActivityView, @@ -19,7 +111,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do end def render("activity.json", %{activity: %{data: %{"type" => "Delete"}} = activity} = opts) do - user = User.get_cached_by_ap_id(activity.data["actor"]) + user = get_user(activity.data["actor"], opts) created_at = activity.data["published"] |> Utils.date_to_asctime() %{ @@ -39,11 +131,11 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do end def render("activity.json", %{activity: %{data: %{"type" => "Follow"}} = activity} = opts) do - user = User.get_cached_by_ap_id(activity.data["actor"]) + user = get_user(activity.data["actor"], opts) created_at = activity.data["published"] || DateTime.to_iso8601(activity.inserted_at) created_at = created_at |> Utils.date_to_asctime() - followed = User.get_cached_by_ap_id(activity.data["object"]) + followed = get_user(activity.data["object"], opts) text = "#{user.nickname} started following #{followed.nickname}" %{ @@ -62,7 +154,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do end def render("activity.json", %{activity: %{data: %{"type" => "Announce"}} = activity} = opts) do - user = User.get_by_ap_id(activity.data["actor"]) + user = get_user(activity.data["actor"], opts) created_at = activity.data["published"] |> Utils.date_to_asctime() announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"]) @@ -80,14 +172,14 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do "uri" => "tag:#{activity.data["id"]}:objectType=note", "created_at" => created_at, "retweeted_status" => retweeted_status, - "statusnet_conversation_id" => conversation_id(announced_activity), + "statusnet_conversation_id" => get_context_id(announced_activity, opts), "external_url" => activity.data["id"], "activity_type" => "repeat" } end def render("activity.json", %{activity: %{data: %{"type" => "Like"}} = activity} = opts) do - user = User.get_cached_by_ap_id(activity.data["actor"]) + user = get_user(activity.data["actor"], opts) liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"]) created_at = @@ -115,8 +207,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do "activity.json", %{activity: %{data: %{"type" => "Create", "object" => object}} = activity} = opts ) do - actor = get_in(activity.data, ["actor"]) - user = User.get_cached_by_ap_id(actor) + user = get_user(activity.data["actor"], opts) created_at = object["published"] |> Utils.date_to_asctime() like_count = object["like_count"] || 0 @@ -126,11 +217,11 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do attentions = activity.recipients - |> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end) + |> Enum.map(fn ap_id -> get_user(ap_id, opts) end) |> Enum.filter(& &1) |> Enum.map(fn user -> UserView.render("show.json", %{user: user, for: opts[:for]}) end) - conversation_id = conversation_id(activity) + conversation_id = get_context_id(activity, opts) tags = activity.data["object"]["tag"] || [] possibly_sensitive = activity.data["object"]["sensitive"] || Enum.member?(tags, "nsfw") @@ -174,12 +265,4 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do "possibly_sensitive" => possibly_sensitive } end - - defp conversation_id(activity) do - with context when not is_nil(context) <- activity.data["context"] do - TwitterAPI.context_to_conversation_id(context) - else - _e -> nil - end - end end diff --git a/lib/pleroma/web/twitter_api/views/notification_view.ex b/lib/pleroma/web/twitter_api/views/notification_view.ex new file mode 100644 index 000000000..f41edea0b --- /dev/null +++ b/lib/pleroma/web/twitter_api/views/notification_view.ex @@ -0,0 +1,65 @@ +defmodule Pleroma.Web.TwitterAPI.NotificationView do + use Pleroma.Web, :view + alias Pleroma.{Notification, User} + alias Pleroma.Web.CommonAPI.Utils + alias Pleroma.Web.MediaProxy + alias Pleroma.Web.TwitterAPI.UserView + alias Pleroma.Web.TwitterAPI.ActivityView + + defp get_user(ap_id, opts) do + cond do + user = opts[:users][ap_id] -> + user + + String.ends_with?(ap_id, "/followers") -> + nil + + ap_id == "https://www.w3.org/ns/activitystreams#Public" -> + nil + + true -> + User.get_cached_by_ap_id(ap_id) + end + end + + def render("notification.json", %{notifications: notifications, for: user}) do + render_many( + notifications, + Pleroma.Web.TwitterAPI.NotificationView, + "notification.json", + for: user + ) + end + + def render( + "notification.json", + %{ + notification: %Notification{ + id: id, + seen: seen, + activity: activity, + inserted_at: created_at + }, + for: user + } = opts + ) do + ntype = + case activity.data["type"] do + "Create" -> "mention" + "Like" -> "like" + "Announce" -> "repeat" + "Follow" -> "follow" + end + + from = get_user(activity.data["actor"], opts) + + %{ + "id" => id, + "ntype" => ntype, + "notice" => ActivityView.render("activity.json", %{activity: activity, for: user}), + "from_profile" => UserView.render("show.json", %{user: from, for: user}), + "is_seen" => if(seen, do: 1, else: 0), + "created_at" => created_at |> Utils.format_naive_asctime() + } + end +end diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index e45c0ed8d..dc9ad2014 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -81,7 +81,10 @@ defmodule Pleroma.Web.WebFinger do "href" => user.ap_id }, %{"rel" => "salmon", "href" => OStatus.salmon_path(user)}, - %{"rel" => "magic-public-key", "href" => "data:application/magic-public-key,#{magic_key}"}, + %{ + "rel" => "magic-public-key", + "href" => "data:application/magic-public-key,#{magic_key}" + }, %{"rel" => "self", "type" => "application/activity+json", "href" => user.ap_id}, %{ "rel" => "http://ostatus.org/schema/1.0/subscribe", diff --git a/lib/pleroma/web/xml/xml.ex b/lib/pleroma/web/xml/xml.ex index b85712d65..8b609f695 100644 --- a/lib/pleroma/web/xml/xml.ex +++ b/lib/pleroma/web/xml/xml.ex @@ -4,14 +4,20 @@ defmodule Pleroma.Web.XML do def string_from_xpath(_, :error), do: nil def string_from_xpath(xpath, doc) do - {:xmlObj, :string, res} = :xmerl_xpath.string('string(#{xpath})', doc) + try do + {:xmlObj, :string, res} = :xmerl_xpath.string('string(#{xpath})', doc) - res = - res - |> to_string - |> String.trim() + res = + res + |> to_string + |> String.trim() - if res == "", do: nil, else: res + if res == "", do: nil, else: res + catch + e -> + Logger.debug("Couldn't find xpath #{xpath} in XML doc") + nil + end end def parse_document(text) do |