From d23f3e3cf3c9a0051532493c60dbd9a7557bae81 Mon Sep 17 00:00:00 2001 From: Roger Braun Date: Tue, 18 Apr 2017 18:41:51 +0200 Subject: [PATCH] Add webfinger and basic feed support. --- lib/pleroma/web/ostatus/feed_representer.ex | 26 +++++++++++++ lib/pleroma/web/ostatus/ostatus.ex | 14 +++++++ lib/pleroma/web/ostatus/ostatus_controller.ex | 26 +++++++++++++ lib/pleroma/web/ostatus/user_representer.ex | 21 +++++----- lib/pleroma/web/router.ex | 10 +++++ lib/pleroma/web/web_finger/web_finger.ex | 3 +- test/web/ostatus/feed_representer_test.exs | 39 +++++++++++++++++++ test/web/ostatus/ostatus_controller_test.exs | 15 +++++++ test/web/ostatus/user_representer_test.exs | 23 ++++++++--- 9 files changed, 160 insertions(+), 17 deletions(-) create mode 100644 lib/pleroma/web/ostatus/feed_representer.ex create mode 100644 lib/pleroma/web/ostatus/ostatus.ex create mode 100644 lib/pleroma/web/ostatus/ostatus_controller.ex create mode 100644 test/web/ostatus/feed_representer_test.exs create mode 100644 test/web/ostatus/ostatus_controller_test.exs diff --git a/lib/pleroma/web/ostatus/feed_representer.ex b/lib/pleroma/web/ostatus/feed_representer.ex new file mode 100644 index 000000000..cb76022fe --- /dev/null +++ b/lib/pleroma/web/ostatus/feed_representer.ex @@ -0,0 +1,26 @@ +defmodule Pleroma.Web.OStatus.FeedRepresenter do + alias Pleroma.Web.OStatus + alias Pleroma.Web.OStatus.UserRepresenter + + def to_simple_form(user, activities, users) do + most_recent_update = List.first(activities).updated_at + |> NaiveDateTime.to_iso8601 + + h = fn(str) -> [to_charlist(str)] end + + entries = [] + [{ + :feed, [ + xmlns: 'http://www.w3.org/2005/Atom', + "xmlns:activity": 'http://activitystrea.ms/spec/1.0/' + ], [ + {:id, h.(OStatus.feed_path(user))}, + {:title, ['#{user.nickname}\'s timeline']}, + {:updated, h.(most_recent_update)}, + {:entries, []}, + {:link, [rel: 'hub', href: h.(OStatus.pubsub_path)], []}, + {:author, UserRepresenter.to_simple_form(user)} + ] + }] + end +end diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex new file mode 100644 index 000000000..9fcbe6cb0 --- /dev/null +++ b/lib/pleroma/web/ostatus/ostatus.ex @@ -0,0 +1,14 @@ +defmodule Pleroma.Web.OStatus do + alias Pleroma.Web + + def feed_path(user) do + "#{user.ap_id}/feed.atom" + end + + def pubsub_path() do + "#{Web.base_url}/push/hub" + end + + def user_path(user) do + end +end diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex new file mode 100644 index 000000000..ff6d7301a --- /dev/null +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -0,0 +1,26 @@ +defmodule Pleroma.Web.OStatus.OStatusController do + use Pleroma.Web, :controller + + alias Pleroma.{User, Activity} + alias Pleroma.Web.OStatus.FeedRepresenter + alias Pleroma.Repo + import Ecto.Query + + def feed(conn, %{"nickname" => nickname}) do + user = User.get_cached_by_nickname(nickname) + query = from activity in Activity, + where: fragment("? @> ?", activity.data, ^%{actor: user.ap_id}), + limit: 20, + order_by: [desc: :inserted_at] + + activities = query + |> Repo.all + + response = FeedRepresenter.to_simple_form(user, activities, [user]) + |> :xmerl.export_simple(:xmerl_xml) + + conn + |> put_resp_content_type("application/atom+xml") + |> send_resp(200, response) + end +end diff --git a/lib/pleroma/web/ostatus/user_representer.ex b/lib/pleroma/web/ostatus/user_representer.ex index 66fc6e053..e7ee4cfeb 100644 --- a/lib/pleroma/web/ostatus/user_representer.ex +++ b/lib/pleroma/web/ostatus/user_representer.ex @@ -1,14 +1,15 @@ defmodule Pleroma.Web.OStatus.UserRepresenter do alias Pleroma.User - def to_tuple(user, wrapper \\ :author) do - { - wrapper, [ - { :id, user.ap_id }, - { :"activity:object", "http://activitystrea.ms/schema/1.0/person" }, - { :uri, user.ap_id }, - { :name, user.nickname }, - { :link, %{rel: "avatar", href: User.avatar_url(user)}} - ] - } + def to_simple_form(user) do + ap_id = to_charlist(user.ap_id) + nickname = to_charlist(user.nickname) + avatar_url = to_charlist(User.avatar_url(user)) + [ + { :id, [ap_id] }, + { :"activity:object", ['http://activitystrea.ms/schema/1.0/person'] }, + { :uri, [ap_id] }, + { :name, [nickname] }, + { :link, [rel: 'avatar', href: avatar_url], []} + ] end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 99d1f69c2..cc1f0e165 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -54,6 +54,16 @@ defmodule Pleroma.Web.Router do post "/qvitter/update_avatar", TwitterAPI.Controller, :update_avatar end + pipeline :ostatus do + plug :accepts, ["xml", "atom"] + end + + scope "/users", Pleroma.Web do + pipe_through :ostatus + + get "/:nickname/feed", OStatus.OStatusController, :feed + end + scope "/.well-known", Pleroma.Web do pipe_through :well_known diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index 258ff7671..eb540e92a 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -1,6 +1,7 @@ defmodule Pleroma.Web.WebFinger do alias Pleroma.XmlBuilder alias Pleroma.User + alias Pleroma.Web.OStatus def host_meta() do base_url = Pleroma.Web.base_url @@ -30,7 +31,7 @@ defmodule Pleroma.Web.WebFinger do [ {:Subject, "acct:#{user.nickname}@#{Pleroma.Web.host}"}, {:Alias, user.ap_id}, - {:Link, %{rel: "http://schemas.google.com/g/2010#updates-from", type: "application/atom+xml", href: "#{user.ap_id}.atom"}} + {:Link, %{rel: "http://schemas.google.com/g/2010#updates-from", type: "application/atom+xml", href: OStatus.feed_path(user)}} ] } |> XmlBuilder.to_doc diff --git a/test/web/ostatus/feed_representer_test.exs b/test/web/ostatus/feed_representer_test.exs new file mode 100644 index 000000000..e252eca9f --- /dev/null +++ b/test/web/ostatus/feed_representer_test.exs @@ -0,0 +1,39 @@ +defmodule Pleroma.Web.OStatus.FeedRepresenterTest do + use Pleroma.DataCase + import Pleroma.Factory + alias Pleroma.User + alias Pleroma.Web.OStatus.{FeedRepresenter, UserRepresenter} + alias Pleroma.Web.OStatus + + test "returns a feed of the last 20 items of the user" do + note_activity = insert(:note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + + tuple = FeedRepresenter.to_simple_form(user, [note_activity], [user]) + + most_recent_update = note_activity.updated_at + |> NaiveDateTime.to_iso8601 + + res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary + user_xml = UserRepresenter.to_simple_form(user) + |> :xmerl.export_simple_content(:xmerl_xml) + + expected = """ + + #{OStatus.feed_path(user)} + #{user.nickname}'s timeline + #{most_recent_update} + + + + #{user_xml} + + + """ + assert clean(res) == clean(expected) + end + + defp clean(string) do + String.replace(string, ~r/\s/, "") + end +end diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs new file mode 100644 index 000000000..229cd9b1e --- /dev/null +++ b/test/web/ostatus/ostatus_controller_test.exs @@ -0,0 +1,15 @@ +defmodule Pleroma.Web.OStatus.OStatusControllerTest do + use Pleroma.Web.ConnCase + import Pleroma.Factory + alias Pleroma.User + + test "gets a feed", %{conn: conn} do + note_activity = insert(:note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + + conn = conn + |> get("/users/#{user.nickname}/feed.atom") + + assert response(conn, 200) + end +end diff --git a/test/web/ostatus/user_representer_test.exs b/test/web/ostatus/user_representer_test.exs index 02a4b5b14..a401a56da 100644 --- a/test/web/ostatus/user_representer_test.exs +++ b/test/web/ostatus/user_representer_test.exs @@ -3,15 +3,26 @@ defmodule Pleroma.Web.OStatus.UserRepresenterTest do alias Pleroma.Web.OStatus.UserRepresenter import Pleroma.Factory + alias Pleroma.User test "returns a user with id, uri, name and link" do user = build(:user) - tuple = UserRepresenter.to_tuple(user) - {:author, author} = tuple + tuple = UserRepresenter.to_simple_form(user) - [:id, :uri, :name, :link] - |> Enum.each(fn (tag) -> - assert Enum.find(author, fn(e) -> tag == elem(e, 0) end) - end) + res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary + + expected = """ + #{user.ap_id} + http://activitystrea.ms/schema/1.0/person + #{user.ap_id} + #{user.nickname} + + """ + + assert clean(res) == clean(expected) + end + + defp clean(string) do + String.replace(string, ~r/\s/, "") end end