X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;ds=sidebyside;f=lib%2Fpleroma%2Fweb%2Ftwitter_api%2Ftwitter_api.ex;h=c12cd7f8aa14a8912e89b69e04609a9e31897b38;hb=a61e8ac15473aca6d0ec9ef20df981bcef9d5897;hp=0217b28d64139a815636f36a3908b3e1605fd732;hpb=443381d0a05849d7dcdc0abb11844cb4d4a75a55;p=akkoma
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index 0217b28d6..c12cd7f8a 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -2,190 +2,260 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
alias Pleroma.{User, Activity, Repo, Object}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter
-
+ alias Pleroma.Web.TwitterAPI.UserView
+ alias Pleroma.Web.{OStatus, CommonAPI}
import Ecto.Query
- def create_status(user = %User{}, data = %{}) do
- attachments = Enum.map(data["media_ids"] || [], fn (media_id) ->
- Repo.get(Object, media_id).data
- end)
-
- context = ActivityPub.generate_context_id
-
- content = HtmlSanitizeEx.strip_tags(data["status"])
-
- mentions = parse_mentions(content)
-
- default_to = [
- User.ap_followers(user),
- "https://www.w3.org/ns/activitystreams#Public"
- ]
-
- to = default_to ++ Enum.map(mentions, fn ({_, %{ap_id: ap_id}}) -> ap_id end)
+ @httpoison Application.get_env(:pleroma, :httpoison)
- content_html = add_user_links(content, mentions)
-
- date = make_date()
-
- activity = %{
- "type" => "Create",
- "to" => to,
- "actor" => user.ap_id,
- "object" => %{
- "type" => "Note",
- "to" => to,
- "content" => content_html,
- "published" => date,
- "context" => context,
- "attachment" => attachments
- },
- "published" => date,
- "context" => context
- }
+ def create_status(%User{} = user, %{"status" => _} = data) do
+ CommonAPI.post(user, data)
+ end
- # Wire up reply info.
- activity = with inReplyToId when not is_nil(inReplyToId) <- data["in_reply_to_status_id"],
- inReplyTo <- Repo.get(Activity, inReplyToId),
- context <- inReplyTo.data["context"]
- do
-
- to = activity["to"] ++ [inReplyTo.data["actor"]]
-
- activity
- |> put_in(["to"], to)
- |> put_in(["context"], context)
- |> put_in(["object", "context"], context)
- |> put_in(["object", "inReplyTo"], inReplyTo.data["object"]["id"])
- |> put_in(["object", "inReplyToStatusId"], inReplyToId)
- |> put_in(["statusnetConversationId"], inReplyTo.data["statusnetConversationId"])
- |> put_in(["object", "statusnetConversationId"], inReplyTo.data["statusnetConversationId"])
- else _e ->
- activity
- end
-
- with {:ok, activity} <- ActivityPub.insert(activity) do
- add_conversation_id(activity)
+ def follow(%User{} = follower, params) do
+ with {:ok, %User{} = followed} <- get_user(params),
+ {:ok, follower} <- User.follow(follower, followed),
+ {:ok, activity} <- ActivityPub.follow(follower, followed) do
+ {:ok, follower, followed, activity}
+ else
+ err -> err
end
end
- def fetch_friend_statuses(user, opts \\ %{}) do
- ActivityPub.fetch_activities([user.ap_id | user.following], opts)
- |> activities_to_statuses(%{for: user})
+ def unfollow(%User{} = follower, params) do
+ with {:ok, %User{} = unfollowed} <- get_user(params),
+ {:ok, follower, follow_activity} <- User.unfollow(follower, unfollowed),
+ {:ok, _activity} <-
+ ActivityPub.insert(%{
+ "type" => "Undo",
+ "actor" => follower.ap_id,
+ # get latest Follow for these users
+ "object" => follow_activity.data["id"],
+ "published" => make_date()
+ }) do
+ {:ok, follower, unfollowed}
+ else
+ err -> err
+ end
end
- def fetch_public_statuses(user, opts \\ %{}) do
- ActivityPub.fetch_public_activities(opts)
- |> activities_to_statuses(%{for: user})
+ 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 fetch_conversation(user, id) do
- query = from activity in Activity,
- where: fragment("? @> ?", activity.data, ^%{ statusnetConversationId: id}),
- limit: 1
-
- with %Activity{} = activity <- Repo.one(query),
- context <- activity.data["context"],
- activities <- ActivityPub.fetch_activities_for_context(context),
- statuses <- activities |> activities_to_statuses(%{for: user})
- do
- statuses
- else e ->
- IO.inspect(e)
- []
+ 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 fetch_status(user, id) do
- with %Activity{} = activity <- Repo.get(Activity, id) do
- activity_to_status(activity, %{for: user})
+ 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) do
+ {:ok, activity}
end
end
- def follow(%User{} = follower, followed_id) do
- with %User{} = followed <- Repo.get(User, followed_id),
- { :ok, follower } <- User.follow(follower, followed),
- { :ok, activity } <- ActivityPub.insert(%{
- "type" => "Follow",
- "actor" => follower.ap_id,
- "object" => followed.ap_id,
- "published" => make_date()
- })
- do
- { :ok, follower, followed, activity }
+ def fav(%User{} = user, ap_id_or_id) do
+ with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user),
+ %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
+ {:ok, activity}
end
end
- def unfollow(%User{} = follower, followed_id) do
- with %User{} = followed <- Repo.get(User, followed_id),
- { :ok, follower } <- User.unfollow(follower, followed)
- do
- { :ok, follower, followed }
+ def unfav(%User{} = user, ap_id_or_id) do
+ with {:ok, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user),
+ %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
+ {:ok, activity}
end
end
- def upload(%Plug.Upload{} = file) do
+ def upload(%Plug.Upload{} = file, format \\ "xml") do
{:ok, object} = ActivityPub.upload(file)
url = List.first(object.data["url"])
href = url["href"]
type = url["mediaType"]
- # Fake this as good as possible...
- """
-
-
- #{object.id}
- #{object.id}
- #{object.id}
- #{href}
- #{href}
-
-
- """
+ case format do
+ "xml" ->
+ # Fake this as good as possible...
+ """
+
+
+ #{object.id}
+ #{object.id}
+ #{object.id}
+ #{href}
+ #{href}
+
+
+ """
+
+ "json" ->
+ %{
+ media_id: object.id,
+ media_id_string: "#{object.id}}",
+ media_url: href,
+ size: 0
+ }
+ |> Jason.encode!()
+ end
end
- def parse_mentions(text) do
- # Modified from https://www.w3.org/TR/html5/forms.html#valid-e-mail-address
- regex = ~r/@[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@?[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/
+ def register_user(params) do
+ params = %{
+ nickname: params["nickname"],
+ name: params["fullname"],
+ bio: params["bio"],
+ email: params["email"],
+ password: params["password"],
+ password_confirmation: params["confirm"]
+ }
+
+ changeset = User.register_changeset(%User{}, params)
- Regex.scan(regex, text)
- |> List.flatten
- |> Enum.uniq
- |> Enum.map(fn ("@" <> match = full_match) -> {full_match, Repo.get_by(User, nickname: match)} end)
- |> Enum.filter(fn ({_match, user}) -> user end)
- end
+ with {:ok, user} <- Repo.insert(changeset) do
+ {:ok, user}
+ else
+ {:error, changeset} ->
+ errors =
+ Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
+ |> Jason.encode!()
- def add_user_links(text, mentions) do
- Enum.reduce(mentions, text, fn ({match, %User{ap_id: ap_id}}, text) -> String.replace(text, match, "#{match}") end)
+ {:error, %{error: errors}}
+ end
end
- defp add_conversation_id(activity) do
- if is_integer(activity.data["statusnetConversationId"]) do
- {:ok, activity}
+ def get_by_id_or_nickname(id_or_nickname) do
+ if !is_integer(id_or_nickname) && :error == Integer.parse(id_or_nickname) do
+ Repo.get_by(User, nickname: id_or_nickname)
else
- data = activity.data
- |> put_in(["object", "statusnetConversationId"], activity.id)
- |> put_in(["statusnetConversationId"], activity.id)
+ Repo.get(User, id_or_nickname)
+ end
+ end
- changeset = Ecto.Changeset.change(activity, data: data)
- Repo.update(changeset)
+ def get_user(user \\ nil, params) do
+ case params do
+ %{"user_id" => user_id} ->
+ case target = get_by_id_or_nickname(user_id) do
+ nil ->
+ {:error, "No user with such user_id"}
+
+ _ ->
+ {:ok, target}
+ end
+
+ %{"screen_name" => nickname} ->
+ case target = Repo.get_by(User, nickname: nickname) do
+ nil ->
+ {:error, "No user with such screen_name"}
+
+ _ ->
+ {:ok, target}
+ end
+
+ _ ->
+ if user do
+ {:ok, user}
+ else
+ {:error, "You need to specify screen_name or user_id"}
+ end
end
end
- defp activities_to_statuses(activities, opts) do
- Enum.map(activities, fn(activity) ->
- activity_to_status(activity, opts)
- end)
+ defp parse_int(string, default)
+
+ defp parse_int(string, default) when is_binary(string) do
+ with {n, _} <- Integer.parse(string) do
+ n
+ else
+ _e -> default
+ end
end
- defp activity_to_status(activity, opts) do
- actor = get_in(activity.data, ["actor"])
- user = Repo.get_by!(User, ap_id: actor)
- mentioned_users = Repo.all(from user in User, where: user.ap_id in ^activity.data["to"])
- ActivityRepresenter.to_map(activity, Map.merge(opts, %{user: user, mentioned: mentioned_users}))
+ defp parse_int(_, default), do: default
+
+ def search(user, %{"q" => query} = params) do
+ limit = parse_int(params["rpp"], 20)
+ page = parse_int(params["page"], 1)
+ offset = (page - 1) * limit
+
+ q =
+ 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: ^limit,
+ offset: ^offset,
+ # this one isn't indexed so psql won't take the wrong index.
+ order_by: [desc: :inserted_at]
+ )
+
+ activities = Repo.all(q)
end
defp make_date do
- DateTime.utc_now() |> DateTime.to_iso8601
+ 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
+ else
+ _e ->
+ changeset = Object.context_mapping(context)
+
+ case Repo.insert(changeset) do
+ {:ok, %{id: id}} ->
+ id
+
+ # 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).id
+ end
+ end
+ end
+
+ def conversation_id_to_context(id) do
+ with %Object{data: %{"id" => context}} <- Repo.get(Object, id) do
+ context
+ else
+ _e ->
+ {:error, "No such conversation"}
+ end
+ end
+
+ def get_external_profile(for_user, uri) do
+ with %User{} = user <- User.get_or_fetch(uri) do
+ spawn(fn ->
+ with url <- user.info["topic"],
+ {:ok, %{body: body}} <-
+ @httpoison.get(url, [], follow_redirect: true, timeout: 10000, recv_timeout: 20000) do
+ OStatus.handle_incoming(body)
+ end
+ end)
+
+ {:ok, UserView.render("show.json", %{user: user, for: for_user})}
+ else
+ _e ->
+ {:error, "Couldn't find user"}
+ end
end
end