Merge branch 'feature/activitypub' into 'develop'
Feature/activitypub See merge request pleroma/pleroma!67
This commit is contained in:
commit
460062f2b0
79 changed files with 3485 additions and 169 deletions
|
@ -27,7 +27,8 @@
|
|||
metadata: [:request_id]
|
||||
|
||||
config :mime, :types, %{
|
||||
"application/xrd+xml" => ["xrd+xml"]
|
||||
"application/xrd+xml" => ["xrd+xml"],
|
||||
"application/activity+json" => ["activity+json"]
|
||||
}
|
||||
|
||||
config :pleroma, :websub, Pleroma.Web.Websub
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# watchers to your application. For example, we use it
|
||||
# with brunch.io to recompile .js and .css sources.
|
||||
config :pleroma, Pleroma.Web.Endpoint,
|
||||
http: [port: 4000],
|
||||
http: [port: 4000, protocol_options: [max_request_line_length: 8192, max_header_value_length: 8192]],
|
||||
protocol: "http",
|
||||
debug_errors: true,
|
||||
code_reloader: true,
|
||||
|
|
25
lib/mix/tasks/fix_ap_users.ex
Normal file
25
lib/mix/tasks/fix_ap_users.ex
Normal file
|
@ -0,0 +1,25 @@
|
|||
defmodule Mix.Tasks.FixApUsers do
|
||||
use Mix.Task
|
||||
import Mix.Ecto
|
||||
import Ecto.Query
|
||||
alias Pleroma.{Repo, User}
|
||||
|
||||
@shortdoc "Grab all ap users again"
|
||||
def run([]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
q = from u in User,
|
||||
where: fragment("? @> ?", u.info, ^%{"ap_enabled" => true}),
|
||||
where: u.local == false
|
||||
users = Repo.all(q)
|
||||
|
||||
Enum.each(users, fn(user) ->
|
||||
try do
|
||||
IO.puts("Fetching #{user.nickname}")
|
||||
Pleroma.Web.ActivityPub.Transmogrifier.upgrade_user_from_ap_id(user.ap_id, false)
|
||||
rescue
|
||||
e -> IO.inspect(e)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Activity do
|
|||
field :data, :map
|
||||
field :local, :boolean, default: true
|
||||
field :actor, :string
|
||||
field :recipients, {:array, :string}
|
||||
has_many :notifications, Notification, on_delete: :delete_all
|
||||
|
||||
timestamps()
|
||||
|
|
27
lib/pleroma/plugs/http_signature.ex
Normal file
27
lib/pleroma/plugs/http_signature.ex
Normal file
|
@ -0,0 +1,27 @@
|
|||
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
||||
alias Pleroma.Web.HTTPSignatures
|
||||
import Plug.Conn
|
||||
require Logger
|
||||
|
||||
def init(options) do
|
||||
options
|
||||
end
|
||||
|
||||
def call(%{assigns: %{valid_signature: true}} = conn, opts) do
|
||||
conn
|
||||
end
|
||||
|
||||
def call(conn, opts) do
|
||||
user = conn.params["actor"]
|
||||
Logger.debug("Checking sig for #{user}")
|
||||
if get_req_header(conn, "signature") do
|
||||
conn = conn
|
||||
|> put_req_header("(request-target)", String.downcase("#{conn.method}") <> " #{conn.request_path}")
|
||||
|
||||
assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
|
||||
else
|
||||
Logger.debug("No signature header!")
|
||||
conn
|
||||
end
|
||||
end
|
||||
end
|
|
@ -80,9 +80,15 @@ def remote_user_creation(params) do
|
|||
|> validate_length(:name, max: 100)
|
||||
|> put_change(:local, false)
|
||||
if changes.valid? do
|
||||
followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
|
||||
changes
|
||||
|> put_change(:follower_address, followers)
|
||||
case changes.changes[:info]["source_data"] do
|
||||
%{"followers" => followers} ->
|
||||
changes
|
||||
|> put_change(:follower_address, followers)
|
||||
_ ->
|
||||
followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
|
||||
changes
|
||||
|> put_change(:follower_address, followers)
|
||||
end
|
||||
else
|
||||
changes
|
||||
end
|
||||
|
@ -97,6 +103,15 @@ def update_changeset(struct, params \\ %{}) do
|
|||
|> validate_length(:name, min: 1, max: 100)
|
||||
end
|
||||
|
||||
def upgrade_changeset(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [:bio, :name, :info, :follower_address, :avatar])
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/)
|
||||
|> validate_length(:bio, max: 5000)
|
||||
|> validate_length(:name, max: 100)
|
||||
end
|
||||
|
||||
def password_update_changeset(struct, params) do
|
||||
changeset = struct
|
||||
|> cast(params, [:password, :password_confirmation])
|
||||
|
@ -144,11 +159,12 @@ def register_changeset(struct, params \\ %{}) do
|
|||
|
||||
def follow(%User{} = follower, %User{info: info} = followed) do
|
||||
ap_followers = followed.follower_address
|
||||
|
||||
if following?(follower, followed) or info["deactivated"] do
|
||||
{:error,
|
||||
"Could not follow user: #{followed.nickname} is already on your list."}
|
||||
else
|
||||
if !followed.local && follower.local do
|
||||
if !followed.local && follower.local && !ap_enabled?(followed) do
|
||||
Websub.subscribe(follower, followed)
|
||||
end
|
||||
|
||||
|
@ -202,6 +218,11 @@ def update_and_set_cache(changeset) do
|
|||
end
|
||||
end
|
||||
|
||||
def invalidate_cache(user) do
|
||||
Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
|
||||
Cachex.del(:user_cache, "nickname:#{user.nickname}")
|
||||
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)
|
||||
|
@ -221,22 +242,30 @@ def get_cached_user_info(user) do
|
|||
Cachex.get!(:user_cache, key, fallback: fn(_) -> user_info(user) end)
|
||||
end
|
||||
|
||||
def fetch_by_nickname(nickname) do
|
||||
ap_try = ActivityPub.make_user_from_nickname(nickname)
|
||||
|
||||
case ap_try do
|
||||
{:ok, user} -> {:ok, user}
|
||||
_ -> OStatus.make_user(nickname)
|
||||
end
|
||||
end
|
||||
|
||||
def get_or_fetch_by_nickname(nickname) do
|
||||
with %User{} = user <- get_by_nickname(nickname) do
|
||||
user
|
||||
else _e ->
|
||||
with [_nick, _domain] <- String.split(nickname, "@"),
|
||||
{:ok, user} <- OStatus.make_user(nickname) do
|
||||
{:ok, user} <- fetch_by_nickname(nickname) do
|
||||
user
|
||||
else _e -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: these queries could be more efficient if the type in postgresql wasn't map, but array.
|
||||
def get_followers(%User{id: id, follower_address: follower_address}) do
|
||||
q = from u in User,
|
||||
where: fragment("? @> ?", u.following, ^follower_address ),
|
||||
where: ^follower_address in u.following,
|
||||
where: u.id != ^id
|
||||
|
||||
{:ok, Repo.all(q)}
|
||||
|
@ -275,7 +304,7 @@ def update_note_count(%User{} = user) do
|
|||
|
||||
def update_follower_count(%User{} = user) do
|
||||
follower_count_query = from u in User,
|
||||
where: fragment("? @> ?", u.following, ^user.follower_address),
|
||||
where: ^user.follower_address in u.following,
|
||||
where: u.id != ^user.id,
|
||||
select: count(u.id)
|
||||
|
||||
|
@ -288,7 +317,7 @@ def update_follower_count(%User{} = user) do
|
|||
update_and_set_cache(cs)
|
||||
end
|
||||
|
||||
def get_notified_from_activity(%Activity{data: %{"to" => to}}) do
|
||||
def get_notified_from_activity(%Activity{recipients: to}) do
|
||||
query = from u in User,
|
||||
where: u.ap_id in ^to,
|
||||
where: u.local == true
|
||||
|
@ -296,10 +325,10 @@ def get_notified_from_activity(%Activity{data: %{"to" => to}}) do
|
|||
Repo.all(query)
|
||||
end
|
||||
|
||||
def get_recipients_from_activity(%Activity{data: %{"to" => to}}) do
|
||||
def get_recipients_from_activity(%Activity{recipients: to}) do
|
||||
query = from u in User,
|
||||
where: u.ap_id in ^to,
|
||||
or_where: fragment("? \\\?| ?", u.following, ^to)
|
||||
or_where: fragment("? && ?", u.following, ^to)
|
||||
|
||||
query = from u in query,
|
||||
where: u.local == true
|
||||
|
@ -376,4 +405,57 @@ def delete (%User{} = user) do
|
|||
|
||||
:ok
|
||||
end
|
||||
|
||||
def get_or_fetch_by_ap_id(ap_id) do
|
||||
if user = get_by_ap_id(ap_id) do
|
||||
user
|
||||
else
|
||||
ap_try = ActivityPub.make_user_from_ap_id(ap_id)
|
||||
|
||||
case ap_try do
|
||||
{:ok, user} -> user
|
||||
_ ->
|
||||
case OStatus.make_user(ap_id) do
|
||||
{:ok, user} -> user
|
||||
_ -> {:error, "Could not fetch by ap id"}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# AP style
|
||||
def public_key_from_info(%{"source_data" => %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
|
||||
key = :public_key.pem_decode(public_key_pem)
|
||||
|> hd()
|
||||
|> :public_key.pem_entry_decode()
|
||||
|
||||
{:ok, key}
|
||||
end
|
||||
|
||||
# OStatus Magic Key
|
||||
def public_key_from_info(%{"magic_key" => magic_key}) do
|
||||
{:ok, Pleroma.Web.Salmon.decode_key(magic_key)}
|
||||
end
|
||||
|
||||
def get_public_key_for_ap_id(ap_id) do
|
||||
with %User{} = user <- get_or_fetch_by_ap_id(ap_id),
|
||||
{:ok, public_key} <- public_key_from_info(user.info) do
|
||||
{:ok, public_key}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
defp blank?(""), do: nil
|
||||
defp blank?(n), do: n
|
||||
|
||||
def insert_or_update_user(data) do
|
||||
data = data
|
||||
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|
||||
cs = User.remote_user_creation(data)
|
||||
Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
|
||||
end
|
||||
|
||||
def ap_enabled?(%User{info: info}), do: info["ap_enabled"]
|
||||
def ap_enabled?(_), do: false
|
||||
end
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
alias Pleroma.{Activity, Repo, Object, Upload, User, Notification}
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.WebFinger
|
||||
alias Pleroma.Web.Federator
|
||||
alias Pleroma.Web.OStatus
|
||||
import Ecto.Query
|
||||
import Pleroma.Web.ActivityPub.Utils
|
||||
require Logger
|
||||
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
|
||||
def get_recipients(data) do
|
||||
(data["to"] || []) ++ (data["cc"] || [])
|
||||
end
|
||||
|
||||
def insert(map, local \\ true) when is_map(map) 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, actor: map["actor"]})
|
||||
{:ok, activity} = Repo.insert(%Activity{data: map, local: local, actor: map["actor"], recipients: get_recipients(map)})
|
||||
Notification.create_notifications(activity)
|
||||
stream_out(activity)
|
||||
{:ok, activity}
|
||||
|
@ -30,7 +40,11 @@ def stream_out(activity) do
|
|||
end
|
||||
end
|
||||
|
||||
def create(to, actor, context, object, additional \\ %{}, published \\ nil, local \\ true) do
|
||||
def create(%{to: to, actor: actor, context: context, object: object} = params) do
|
||||
additional = params[:additional] || %{}
|
||||
local = !(params[:local] == false) # only accept false as false value
|
||||
published = params[:published]
|
||||
|
||||
with create_data <- make_create_data(%{to: to, actor: actor, published: published, context: context, object: object}, additional),
|
||||
{:ok, activity} <- insert(create_data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
|
@ -38,6 +52,26 @@ def create(to, actor, context, object, additional \\ %{}, published \\ nil, loca
|
|||
end
|
||||
end
|
||||
|
||||
def accept(%{to: to, actor: actor, object: object} = params) do
|
||||
local = !(params[:local] == false) # only accept false as false value
|
||||
|
||||
with data <- %{"to" => to, "type" => "Accept", "actor" => actor, "object" => object},
|
||||
{:ok, activity} <- insert(data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
|
||||
local = !(params[:local] == false) # only accept false as false value
|
||||
|
||||
with data <- %{"to" => to, "cc" => cc, "type" => "Update", "actor" => actor, "object" => object},
|
||||
{:ok, activity} <- insert(data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
end
|
||||
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" => _}} = object, activity_id \\ nil, local \\ true) do
|
||||
with nil <- get_existing_like(ap_id, object),
|
||||
|
@ -62,7 +96,8 @@ def unlike(%User{} = actor, %Object{} = object) do
|
|||
end
|
||||
|
||||
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),
|
||||
with true <- is_public?(object),
|
||||
announce_data <- make_announce_data(user, object, activity_id),
|
||||
{:ok, activity} <- insert(announce_data, local),
|
||||
{:ok, object} <- add_announce_to_object(activity, object),
|
||||
:ok <- maybe_federate(activity) do
|
||||
|
@ -106,16 +141,26 @@ def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ tru
|
|||
end
|
||||
|
||||
def fetch_activities_for_context(context, opts \\ %{}) do
|
||||
query = from activity in Activity,
|
||||
public = ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
recipients = if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
|
||||
|
||||
query = from activity in Activity
|
||||
query = query
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_recipients(recipients, opts["user"])
|
||||
|
||||
query = from activity in query,
|
||||
where: fragment("?->>'type' = ? and ?->>'context' = ?", activity.data, "Create", activity.data, ^context),
|
||||
order_by: [desc: :id]
|
||||
query = restrict_blocked(query, opts)
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
# TODO: Make this work properly with unlisted.
|
||||
def fetch_public_activities(opts \\ %{}) do
|
||||
public = ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
fetch_activities(public, opts)
|
||||
q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts)
|
||||
q
|
||||
|> Repo.all
|
||||
|> Enum.reverse
|
||||
end
|
||||
|
||||
defp restrict_since(query, %{"since_id" => since_id}) do
|
||||
|
@ -129,12 +174,15 @@ defp restrict_tag(query, %{"tag" => tag}) do
|
|||
end
|
||||
defp restrict_tag(query, _), do: query
|
||||
|
||||
defp restrict_recipients(query, recipients) do
|
||||
Enum.reduce(recipients, query, fn (recipient, q) ->
|
||||
map = %{ to: [recipient] }
|
||||
from activity in q,
|
||||
or_where: fragment(~s(? @> ?), activity.data, ^map)
|
||||
end)
|
||||
defp restrict_recipients(query, [], user), do: query
|
||||
defp restrict_recipients(query, recipients, nil) do
|
||||
from activity in query,
|
||||
where: fragment("? && ?", ^recipients, activity.recipients)
|
||||
end
|
||||
defp restrict_recipients(query, recipients, user) do
|
||||
from activity in query,
|
||||
where: fragment("? && ?", ^recipients, activity.recipients),
|
||||
or_where: activity.actor == ^user.ap_id
|
||||
end
|
||||
|
||||
defp restrict_local(query, %{"local_only" => true}) do
|
||||
|
@ -190,13 +238,13 @@ defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
|
|||
end
|
||||
defp restrict_blocked(query, _), do: query
|
||||
|
||||
def fetch_activities(recipients, opts \\ %{}) do
|
||||
def fetch_activities_query(recipients, opts \\ %{}) do
|
||||
base_query = from activity in Activity,
|
||||
limit: 20,
|
||||
order_by: [fragment("? desc nulls last", activity.id)]
|
||||
|
||||
base_query
|
||||
|> restrict_recipients(recipients)
|
||||
|> restrict_recipients(recipients, opts["user"])
|
||||
|> restrict_tag(opts)
|
||||
|> restrict_since(opts)
|
||||
|> restrict_local(opts)
|
||||
|
@ -207,6 +255,10 @@ def fetch_activities(recipients, opts \\ %{}) do
|
|||
|> restrict_recent(opts)
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_media(opts)
|
||||
end
|
||||
|
||||
def fetch_activities(recipients, opts \\ %{}) do
|
||||
fetch_activities_query(recipients, opts)
|
||||
|> Repo.all
|
||||
|> Enum.reverse
|
||||
end
|
||||
|
@ -215,4 +267,128 @@ def upload(file) do
|
|||
data = Upload.store(file)
|
||||
Repo.insert(%Object{data: data})
|
||||
end
|
||||
|
||||
def user_data_from_user_object(data) do
|
||||
avatar = data["icon"]["url"] && %{
|
||||
"type" => "Image",
|
||||
"url" => [%{"href" => data["icon"]["url"]}]
|
||||
}
|
||||
|
||||
banner = data["image"]["url"] && %{
|
||||
"type" => "Image",
|
||||
"url" => [%{"href" => data["image"]["url"]}]
|
||||
}
|
||||
|
||||
user_data = %{
|
||||
ap_id: data["id"],
|
||||
info: %{
|
||||
"ap_enabled" => true,
|
||||
"source_data" => data,
|
||||
"banner" => banner
|
||||
},
|
||||
avatar: avatar,
|
||||
nickname: "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}",
|
||||
name: data["name"],
|
||||
follower_address: data["followers"],
|
||||
bio: data["summary"]
|
||||
}
|
||||
|
||||
{:ok, user_data}
|
||||
end
|
||||
|
||||
def fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||
with {:ok, %{status_code: 200, body: body}} <- @httpoison.get(ap_id, ["Accept": "application/activity+json"]),
|
||||
{:ok, data} <- Poison.decode(body) do
|
||||
user_data_from_user_object(data)
|
||||
else
|
||||
e -> Logger.error("Could not user at fetch #{ap_id}, #{inspect(e)}")
|
||||
end
|
||||
end
|
||||
|
||||
def make_user_from_ap_id(ap_id) do
|
||||
if user = User.get_by_ap_id(ap_id) do
|
||||
Transmogrifier.upgrade_user_from_ap_id(ap_id)
|
||||
else
|
||||
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||
User.insert_or_update_user(data)
|
||||
else
|
||||
e -> {:error, e}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_user_from_nickname(nickname) do
|
||||
with {:ok, %{"ap_id" => ap_id}} when not is_nil(ap_id) <- WebFinger.finger(nickname) do
|
||||
make_user_from_ap_id(ap_id)
|
||||
else
|
||||
_e -> {:error, "No ap id in webfinger"}
|
||||
end
|
||||
end
|
||||
|
||||
def publish(actor, activity) do
|
||||
followers = if actor.follower_address in activity.recipients do
|
||||
{:ok, followers} = User.get_followers(actor)
|
||||
followers |> Enum.filter(&(!&1.local))
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
remote_inboxes = (Pleroma.Web.Salmon.remote_users(activity) ++ followers)
|
||||
|> Enum.filter(fn (user) -> User.ap_enabled?(user) end)
|
||||
|> Enum.map(fn (%{info: %{"source_data" => data}}) ->
|
||||
(data["endpoints"] && data["endpoints"]["sharedInbox"]) || data["inbox"]
|
||||
end)
|
||||
|> Enum.uniq
|
||||
|
||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
json = Poison.encode!(data)
|
||||
Enum.each remote_inboxes, fn(inbox) ->
|
||||
Federator.enqueue(:publish_single_ap, %{inbox: inbox, json: json, actor: actor, id: activity.data["id"]})
|
||||
end
|
||||
end
|
||||
|
||||
def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
|
||||
Logger.info("Federating #{id} to #{inbox}")
|
||||
host = URI.parse(inbox).host
|
||||
signature = Pleroma.Web.HTTPSignatures.sign(actor, %{host: host, "content-length": byte_size(json)})
|
||||
@httpoison.post(inbox, json, [{"Content-Type", "application/activity+json"}, {"signature", signature}])
|
||||
end
|
||||
|
||||
# TODO:
|
||||
# This will create a Create activity, which we need internally at the moment.
|
||||
def fetch_object_from_id(id) do
|
||||
if object = Object.get_cached_by_ap_id(id) do
|
||||
{:ok, object}
|
||||
else
|
||||
Logger.info("Fetching #{id} via AP")
|
||||
with {:ok, %{body: body, status_code: code}} when code in 200..299 <- @httpoison.get(id, [Accept: "application/activity+json"], follow_redirect: true, timeout: 10000, recv_timeout: 20000),
|
||||
{:ok, data} <- Poison.decode(body),
|
||||
nil <- Object.get_by_ap_id(data["id"]),
|
||||
params <- %{"type" => "Create", "to" => data["to"], "cc" => data["cc"], "actor" => data["attributedTo"], "object" => data},
|
||||
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
|
||||
{:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
|
||||
else
|
||||
object = %Object{} -> {:ok, object}
|
||||
e ->
|
||||
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
|
||||
case OStatus.fetch_activity_from_url(id) do
|
||||
{:ok, [activity | _]} -> {:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
|
||||
e -> e
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def is_public?(activity) do
|
||||
"https://www.w3.org/ns/activitystreams#Public" in (activity.data["to"] ++ (activity.data["cc"] || []))
|
||||
end
|
||||
|
||||
def visible_for_user?(activity, nil) do
|
||||
is_public?(activity)
|
||||
end
|
||||
def visible_for_user?(activity, user) do
|
||||
x = [user.ap_id | user.following]
|
||||
y = (activity.data["to"] ++ (activity.data["cc"] || []))
|
||||
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
|
||||
end
|
||||
end
|
||||
|
|
54
lib/pleroma/web/activity_pub/activity_pub_controller.ex
Normal file
54
lib/pleroma/web/activity_pub/activity_pub_controller.ex
Normal file
|
@ -0,0 +1,54 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||
use Pleroma.Web, :controller
|
||||
alias Pleroma.{User, Repo, Object, Activity}
|
||||
alias Pleroma.Web.ActivityPub.{ObjectView, UserView, Transmogrifier}
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.Federator
|
||||
|
||||
require Logger
|
||||
|
||||
action_fallback :errors
|
||||
|
||||
def user(conn, %{"nickname" => nickname}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|> json(UserView.render("user.json", %{user: user}))
|
||||
end
|
||||
end
|
||||
|
||||
def object(conn, %{"uuid" => uuid}) do
|
||||
with ap_id <- o_status_url(conn, :object, uuid),
|
||||
%Object{} = object <- Object.get_cached_by_ap_id(ap_id) do
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|> json(ObjectView.render("object.json", %{object: object}))
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Ensure that this inbox is a recipient of the message
|
||||
def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
|
||||
Federator.enqueue(:incoming_ap_doc, params)
|
||||
json(conn, "ok")
|
||||
end
|
||||
|
||||
def inbox(conn, params) do
|
||||
headers = Enum.into(conn.req_headers, %{})
|
||||
if !(String.contains?(headers["signature"] || "", params["actor"])) do
|
||||
Logger.info("Signature not from author, relayed message, ignoring.")
|
||||
else
|
||||
Logger.info("Signature error.")
|
||||
Logger.info("Could not validate #{params["actor"]}")
|
||||
Logger.info(inspect(conn.req_headers))
|
||||
end
|
||||
|
||||
json(conn, "ok")
|
||||
end
|
||||
|
||||
def errors(conn, _e) do
|
||||
conn
|
||||
|> put_status(500)
|
||||
|> json("error")
|
||||
end
|
||||
end
|
298
lib/pleroma/web/activity_pub/transmogrifier.ex
Normal file
298
lib/pleroma/web/activity_pub/transmogrifier.ex
Normal file
|
@ -0,0 +1,298 @@
|
|||
defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
@moduledoc """
|
||||
A module to handle coding from internal to wire ActivityPub and back.
|
||||
"""
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
require Logger
|
||||
|
||||
@doc """
|
||||
Modifies an incoming AP object (mastodon format) to our internal format.
|
||||
"""
|
||||
def fix_object(object) do
|
||||
object
|
||||
|> Map.put("actor", object["attributedTo"])
|
||||
|> fix_attachments
|
||||
|> fix_context
|
||||
|> fix_in_reply_to
|
||||
end
|
||||
|
||||
def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object) when not is_nil(in_reply_to_id) do
|
||||
case ActivityPub.fetch_object_from_id(in_reply_to_id) do
|
||||
{:ok, replied_object} ->
|
||||
activity = Activity.get_create_activity_by_object_ap_id(replied_object.data["id"])
|
||||
object
|
||||
|> Map.put("inReplyTo", replied_object.data["id"])
|
||||
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
|
||||
|> Map.put("inReplyToStatusId", activity.id)
|
||||
|> Map.put("conversation", replied_object.data["context"] || object["conversation"])
|
||||
|> Map.put("context", replied_object.data["context"] || object["conversation"])
|
||||
e ->
|
||||
Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
|
||||
object
|
||||
end
|
||||
end
|
||||
def fix_in_reply_to(object), do: object
|
||||
|
||||
def fix_context(object) do
|
||||
object
|
||||
|> Map.put("context", object["conversation"])
|
||||
end
|
||||
|
||||
def fix_attachments(object) do
|
||||
attachments = (object["attachment"] || [])
|
||||
|> Enum.map(fn (data) ->
|
||||
url = [%{"type" => "Link", "mediaType" => data["mediaType"], "href" => data["url"]}]
|
||||
Map.put(data, "url", url)
|
||||
end)
|
||||
|
||||
object
|
||||
|> Map.put("attachment", attachments)
|
||||
end
|
||||
|
||||
# TODO: validate those with a Ecto scheme
|
||||
# - tags
|
||||
# - emoji
|
||||
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
|
||||
with nil <- Activity.get_create_activity_by_object_ap_id(object["id"]),
|
||||
%User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do
|
||||
object = fix_object(data["object"])
|
||||
|
||||
params = %{
|
||||
to: data["to"],
|
||||
object: object,
|
||||
actor: user,
|
||||
context: object["conversation"],
|
||||
local: false,
|
||||
published: data["published"],
|
||||
additional: Map.take(data, [
|
||||
"cc",
|
||||
"id"
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
ActivityPub.create(params)
|
||||
else
|
||||
%Activity{} = activity -> {:ok, activity}
|
||||
_e -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def handle_incoming(%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data) do
|
||||
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
|
||||
%User{} = follower <- User.get_or_fetch_by_ap_id(follower),
|
||||
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
|
||||
ActivityPub.accept(%{to: [follower.ap_id], actor: followed.ap_id, object: data, local: true})
|
||||
User.follow(follower, followed)
|
||||
{:ok, activity}
|
||||
else
|
||||
_e -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def handle_incoming(%{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = data) do
|
||||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{: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
|
||||
_e -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def handle_incoming(%{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = data) do
|
||||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{: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
|
||||
_e -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def handle_incoming(%{"type" => "Update", "object" => %{"type" => "Person"} = object, "actor" => actor_id} = data) do
|
||||
with %User{ap_id: ^actor_id} = actor <- User.get_by_ap_id(object["id"]) do
|
||||
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
|
||||
|
||||
banner = new_user_data[:info]["banner"]
|
||||
update_data = new_user_data
|
||||
|> Map.take([:name, :bio, :avatar])
|
||||
|> Map.put(:info, Map.merge(actor.info, %{"banner" => banner}))
|
||||
|
||||
actor
|
||||
|> User.upgrade_changeset(update_data)
|
||||
|> User.update_and_set_cache()
|
||||
|
||||
ActivityPub.update(%{local: false, to: data["to"] || [], cc: data["cc"] || [], object: object, actor: actor_id})
|
||||
else
|
||||
e ->
|
||||
Logger.error(e)
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Make secure.
|
||||
def handle_incoming(%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data) do
|
||||
object_id = case object_id do
|
||||
%{"id" => id} -> id
|
||||
id -> id
|
||||
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, activity} <- ActivityPub.delete(object, false) do
|
||||
{:ok, activity}
|
||||
else
|
||||
e -> :error
|
||||
end
|
||||
end
|
||||
|
||||
# TODO
|
||||
# Accept
|
||||
# Undo
|
||||
|
||||
def handle_incoming(_), do: :error
|
||||
|
||||
def get_obj_helper(id) do
|
||||
if object = Object.get_by_ap_id(id), do: {:ok, object}, else: nil
|
||||
end
|
||||
|
||||
def prepare_object(object) do
|
||||
object
|
||||
|> set_sensitive
|
||||
|> add_hashtags
|
||||
|> add_mention_tags
|
||||
|> add_attributed_to
|
||||
|> prepare_attachments
|
||||
|> set_conversation
|
||||
end
|
||||
|
||||
@doc
|
||||
"""
|
||||
internal -> Mastodon
|
||||
"""
|
||||
def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
|
||||
object = object
|
||||
|> prepare_object
|
||||
data = data
|
||||
|> Map.put("object", object)
|
||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
|
||||
def prepare_outgoing(%{"type" => type} = data) do
|
||||
data = data
|
||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
|
||||
def add_hashtags(object) do
|
||||
tags = (object["tag"] || [])
|
||||
|> Enum.map fn (tag) -> %{"href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}", "name" => "##{tag}", "type" => "Hashtag"} end
|
||||
|
||||
object
|
||||
|> Map.put("tag", tags)
|
||||
end
|
||||
|
||||
def add_mention_tags(object) do
|
||||
recipients = object["to"] ++ (object["cc"] || [])
|
||||
mentions = recipients
|
||||
|> Enum.map(fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end)
|
||||
|> Enum.filter(&(&1))
|
||||
|> Enum.map(fn(user) -> %{"type" => "Mention", "href" => user.ap_id, "name" => "@#{user.nickname}"} end)
|
||||
|
||||
tags = object["tag"] || []
|
||||
|
||||
object
|
||||
|> Map.put("tag", tags ++ mentions)
|
||||
end
|
||||
|
||||
def set_conversation(object) do
|
||||
Map.put(object, "conversation", object["context"])
|
||||
end
|
||||
|
||||
def set_sensitive(object) do
|
||||
tags = object["tag"] || []
|
||||
Map.put(object, "sensitive", "nsfw" in tags)
|
||||
end
|
||||
|
||||
def add_attributed_to(object) do
|
||||
attributedTo = object["attributedTo"] || object["actor"]
|
||||
|
||||
object
|
||||
|> Map.put("attributedTo", attributedTo)
|
||||
end
|
||||
|
||||
def prepare_attachments(object) do
|
||||
attachments = (object["attachment"] || [])
|
||||
|> Enum.map(fn (data) ->
|
||||
[%{"mediaType" => media_type, "href" => href} | _] = data["url"]
|
||||
%{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"}
|
||||
end)
|
||||
|
||||
object
|
||||
|> Map.put("attachment", attachments)
|
||||
end
|
||||
|
||||
defp user_upgrade_task(user) do
|
||||
old_follower_address = User.ap_followers(user)
|
||||
q = from u in User,
|
||||
where: ^old_follower_address in u.following,
|
||||
update: [set: [following: fragment("array_replace(?,?,?)", u.following, ^old_follower_address, ^user.follower_address)]]
|
||||
Repo.update_all(q, [])
|
||||
|
||||
maybe_retire_websub(user.ap_id)
|
||||
|
||||
# Only do this for recent activties, don't go through the whole db.
|
||||
since = (Repo.aggregate(Activity, :max, :id) || 0) - 100_000
|
||||
q = from a in Activity,
|
||||
where: ^old_follower_address in a.recipients,
|
||||
where: a.id > ^since,
|
||||
update: [set: [recipients: fragment("array_replace(?,?,?)", a.recipients, ^old_follower_address, ^user.follower_address)]]
|
||||
Repo.update_all(q, [])
|
||||
end
|
||||
|
||||
def upgrade_user_from_ap_id(ap_id, async \\ true) do
|
||||
with %User{local: false} = user <- User.get_by_ap_id(ap_id),
|
||||
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||
data = data
|
||||
|> Map.put(:info, Map.merge(user.info, data[:info]))
|
||||
|
||||
already_ap = User.ap_enabled?(user)
|
||||
{:ok, user} = User.upgrade_changeset(user, data)
|
||||
|> Repo.update()
|
||||
|
||||
if !already_ap do
|
||||
# This could potentially take a long time, do it in the background
|
||||
if async do
|
||||
Task.start(fn ->
|
||||
user_upgrade_task(user)
|
||||
end)
|
||||
else
|
||||
user_upgrade_task(user)
|
||||
end
|
||||
end
|
||||
|
||||
{:ok, user}
|
||||
else
|
||||
e -> e
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_retire_websub(ap_id) do
|
||||
# some sanity checks
|
||||
if is_binary(ap_id) && (String.length(ap_id) > 8) do
|
||||
q = from ws in Pleroma.Web.Websub.WebsubClientSubscription,
|
||||
where: fragment("? like ?", ws.topic, ^"#{ap_id}%")
|
||||
Repo.delete_all(q)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -68,7 +68,7 @@ def lazy_put_object_defaults(map) do
|
|||
@doc """
|
||||
Inserts a full object if it is contained in an activity.
|
||||
"""
|
||||
def insert_full_object(%{"object" => object_data}) when is_map(object_data) do
|
||||
def insert_full_object(%{"object" => %{"type" => type} = object_data}) when is_map(object_data) and type in ["Note"] do
|
||||
with {:ok, _} <- Object.create(object_data) do
|
||||
:ok
|
||||
end
|
||||
|
@ -109,6 +109,7 @@ def make_like_data(%User{ap_id: ap_id} = actor, %{data: %{"id" => id}} = object,
|
|||
"actor" => ap_id,
|
||||
"object" => id,
|
||||
"to" => [actor.follower_address, object.data["actor"]],
|
||||
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"context" => object.data["context"]
|
||||
}
|
||||
|
||||
|
@ -150,6 +151,7 @@ def make_follow_data(%User{ap_id: follower_id}, %User{ap_id: followed_id}, activ
|
|||
"type" => "Follow",
|
||||
"actor" => follower_id,
|
||||
"to" => [followed_id],
|
||||
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"object" => followed_id
|
||||
}
|
||||
|
||||
|
@ -177,6 +179,7 @@ def make_announce_data(%User{ap_id: ap_id} = user, %Object{data: %{"id" => id}}
|
|||
"actor" => ap_id,
|
||||
"object" => id,
|
||||
"to" => [user.follower_address, object.data["actor"]],
|
||||
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"context" => object.data["context"]
|
||||
}
|
||||
|
||||
|
@ -205,7 +208,6 @@ def make_unfollow_data(follower, followed, follow_activity) do
|
|||
|
||||
def make_create_data(params, additional) do
|
||||
published = params.published || make_date()
|
||||
|
||||
%{
|
||||
"type" => "Create",
|
||||
"to" => params.to |> Enum.uniq,
|
||||
|
|
27
lib/pleroma/web/activity_pub/views/object_view.ex
Normal file
27
lib/pleroma/web/activity_pub/views/object_view.ex
Normal file
|
@ -0,0 +1,27 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectView do
|
||||
use Pleroma.Web, :view
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
def render("object.json", %{object: object}) do
|
||||
base = %{
|
||||
"@context" => [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
%{
|
||||
"manuallyApprovesFollowers" => "as:manuallyApprovesFollowers",
|
||||
"sensitive" => "as:sensitive",
|
||||
"Hashtag" => "as:Hashtag",
|
||||
"ostatus" => "http://ostatus.org#",
|
||||
"atomUri" => "ostatus:atomUri",
|
||||
"inReplyToAtomUri" => "ostatus:inReplyToAtomUri",
|
||||
"conversation" => "ostatus:conversation",
|
||||
"toot" => "http://joinmastodon.org/ns#",
|
||||
"Emoji" => "toot:Emoji"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
additional = Transmogrifier.prepare_object(object.data)
|
||||
Map.merge(base, additional)
|
||||
end
|
||||
end
|
57
lib/pleroma/web/activity_pub/views/user_view.ex
Normal file
57
lib/pleroma/web/activity_pub/views/user_view.ex
Normal file
|
@ -0,0 +1,57 @@
|
|||
defmodule Pleroma.Web.ActivityPub.UserView do
|
||||
use Pleroma.Web, :view
|
||||
alias Pleroma.Web.Salmon
|
||||
alias Pleroma.Web.WebFinger
|
||||
alias Pleroma.User
|
||||
|
||||
def render("user.json", %{user: user}) do
|
||||
{:ok, user} = WebFinger.ensure_keys_present(user)
|
||||
{:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"])
|
||||
public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
|
||||
public_key = :public_key.pem_encode([public_key])
|
||||
%{
|
||||
"@context" => [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
%{
|
||||
"manuallyApprovesFollowers" => "as:manuallyApprovesFollowers",
|
||||
"sensitive" => "as:sensitive",
|
||||
"Hashtag" => "as:Hashtag",
|
||||
"ostatus" => "http://ostatus.org#",
|
||||
"atomUri" => "ostatus:atomUri",
|
||||
"inReplyToAtomUri" => "ostatus:inReplyToAtomUri",
|
||||
"conversation" => "ostatus:conversation",
|
||||
"toot" => "http://joinmastodon.org/ns#",
|
||||
"Emoji" => "toot:Emoji"
|
||||
}
|
||||
],
|
||||
"id" => user.ap_id,
|
||||
"type" => "Person",
|
||||
"following" => "#{user.ap_id}/following",
|
||||
"followers" => "#{user.ap_id}/followers",
|
||||
"inbox" => "#{user.ap_id}/inbox",
|
||||
"outbox" => "#{user.ap_id}/outbox",
|
||||
"preferredUsername" => user.nickname,
|
||||
"name" => user.name,
|
||||
"summary" => user.bio,
|
||||
"url" => user.ap_id,
|
||||
"manuallyApprovesFollowers" => false,
|
||||
"publicKey" => %{
|
||||
"id" => "#{user.ap_id}#main-key",
|
||||
"owner" => user.ap_id,
|
||||
"publicKeyPem" => public_key
|
||||
},
|
||||
"endpoints" => %{
|
||||
"sharedInbox" => "#{Pleroma.Web.Endpoint.url}/inbox"
|
||||
},
|
||||
"icon" => %{
|
||||
"type" => "Image",
|
||||
"url" => User.avatar_url(user)
|
||||
},
|
||||
"image" => %{
|
||||
"type" => "Image",
|
||||
"url" => User.banner_url(user)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
|
@ -46,24 +46,36 @@ def unfavorite(id_or_ap_id, user) do
|
|||
end
|
||||
end
|
||||
|
||||
def get_visibility(%{"visibility" => visibility}), do: visibility
|
||||
def get_visibility(%{"in_reply_to_status_id" => status_id}) when status_id do
|
||||
inReplyTo = get_replied_to_activity(status_id)
|
||||
Pleroma.Web.MastodonAPI.StatusView.get_visibility(inReplyTo.data["object"])
|
||||
end
|
||||
def get_visibility(_), do: "public"
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@limit Keyword.get(@instance, :limit)
|
||||
def post(user, %{"status" => status} = data) do
|
||||
visibility = get_visibility(data)
|
||||
with status <- String.trim(status),
|
||||
length when length in 1..@limit <- String.length(status),
|
||||
attachments <- attachments_from_ids(data["media_ids"]),
|
||||
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),
|
||||
{to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility),
|
||||
tags <- Formatter.parse_tags(status, data),
|
||||
content_html <- make_content_html(status, mentions, attachments, tags, data["no_attachment_links"]),
|
||||
context <- make_context(inReplyTo),
|
||||
cw <- data["spoiler_text"],
|
||||
object <- make_note_data(user.ap_id, to, context, content_html, attachments, inReplyTo, tags, cw),
|
||||
object <- make_note_data(user.ap_id, to, context, content_html, attachments, inReplyTo, tags, cw, cc),
|
||||
object <- Map.put(object, "emoji", Formatter.get_emoji(status) |> Enum.reduce(%{}, fn({name, file}, acc) -> Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url}#{file}") end)) do
|
||||
res = ActivityPub.create(to, user, context, object)
|
||||
res = ActivityPub.create(%{to: to, actor: user, context: context, object: object, additional: %{"cc" => cc}})
|
||||
User.increase_note_count(user)
|
||||
res
|
||||
end
|
||||
end
|
||||
|
||||
def update(user) do
|
||||
ActivityPub.update(%{local: true, to: [user.follower_address], cc: [], actor: user.ap_id, object: Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,17 +24,34 @@ def attachments_from_ids(ids) do
|
|||
end)
|
||||
end
|
||||
|
||||
def to_for_user_and_mentions(user, mentions, inReplyTo) do
|
||||
default_to = [
|
||||
user.follower_address,
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
]
|
||||
def to_for_user_and_mentions(user, mentions, inReplyTo, "public") do
|
||||
to = ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
|
||||
to = default_to ++ Enum.map(mentions, fn ({_, %{ap_id: ap_id}}) -> ap_id end)
|
||||
mentioned_users = Enum.map(mentions, fn ({_, %{ap_id: ap_id}}) -> ap_id end)
|
||||
cc = [user.follower_address | mentioned_users]
|
||||
if inReplyTo do
|
||||
Enum.uniq([inReplyTo.data["actor"] | to])
|
||||
{to, Enum.uniq([inReplyTo.data["actor"] | cc])}
|
||||
else
|
||||
to
|
||||
{to, cc}
|
||||
end
|
||||
end
|
||||
|
||||
def to_for_user_and_mentions(user, mentions, inReplyTo, "unlisted") do
|
||||
{to, cc} = to_for_user_and_mentions(user, mentions, inReplyTo, "public")
|
||||
{cc, to}
|
||||
end
|
||||
|
||||
def to_for_user_and_mentions(user, mentions, inReplyTo, "private") do
|
||||
{to, cc} = to_for_user_and_mentions(user, mentions, inReplyTo, "direct")
|
||||
{[user.follower_address | to], cc}
|
||||
end
|
||||
|
||||
def to_for_user_and_mentions(user, mentions, inReplyTo, "direct") do
|
||||
mentioned_users = Enum.map(mentions, fn ({_, %{ap_id: ap_id}}) -> ap_id end)
|
||||
if inReplyTo do
|
||||
{Enum.uniq([inReplyTo.data["actor"] | mentioned_users]), []}
|
||||
else
|
||||
{mentioned_users, []}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -99,10 +116,11 @@ def add_user_links(text, mentions) do
|
|||
end)
|
||||
end
|
||||
|
||||
def make_note_data(actor, to, context, content_html, attachments, inReplyTo, tags, cw \\ nil) do
|
||||
def make_note_data(actor, to, context, content_html, attachments, inReplyTo, tags, cw \\ nil, cc \\ []) do
|
||||
object = %{
|
||||
"type" => "Note",
|
||||
"to" => to,
|
||||
"cc" => cc,
|
||||
"content" => content_html,
|
||||
"summary" => cw,
|
||||
"context" => context,
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
defmodule Pleroma.Web.Federator do
|
||||
use GenServer
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Web.{WebFinger, Websub}
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
require Logger
|
||||
|
||||
@websub Application.get_env(:pleroma, :websub)
|
||||
|
@ -44,11 +47,16 @@ def handle(:publish, activity) do
|
|||
Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end)
|
||||
with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||
{:ok, actor} = WebFinger.ensure_keys_present(actor)
|
||||
Logger.debug(fn -> "Sending #{activity.data["id"]} out via salmon" end)
|
||||
Pleroma.Web.Salmon.publish(actor, activity)
|
||||
if ActivityPub.is_public?(activity) do
|
||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via websub" end)
|
||||
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
|
||||
|
||||
Logger.debug(fn -> "Sending #{activity.data["id"]} out via websub" end)
|
||||
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
|
||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via salmon" end)
|
||||
Pleroma.Web.Salmon.publish(actor, activity)
|
||||
end
|
||||
|
||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via AP" end)
|
||||
Pleroma.Web.ActivityPub.ActivityPub.publish(actor, activity)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -58,10 +66,29 @@ def handle(:verify_websub, websub) do
|
|||
end
|
||||
|
||||
def handle(:incoming_doc, doc) do
|
||||
Logger.debug("Got document, trying to parse")
|
||||
Logger.info("Got document, trying to parse")
|
||||
@ostatus.handle_incoming(doc)
|
||||
end
|
||||
|
||||
def handle(:incoming_ap_doc, params) do
|
||||
Logger.info("Handling incoming ap activity")
|
||||
with {:ok, _user} <- ap_enabled_actor(params["actor"]),
|
||||
nil <- Activity.get_by_ap_id(params["id"]),
|
||||
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
|
||||
else
|
||||
%Activity{} ->
|
||||
Logger.info("Already had #{params["id"]}")
|
||||
e ->
|
||||
# Just drop those for now
|
||||
Logger.info("Unhandled activity")
|
||||
Logger.info(Poison.encode!(params, [pretty: 2]))
|
||||
end
|
||||
end
|
||||
|
||||
def handle(:publish_single_ap, params) do
|
||||
ActivityPub.publish_one(params)
|
||||
end
|
||||
|
||||
def handle(:publish_single_websub, %{xml: xml, topic: topic, callback: callback, secret: secret}) do
|
||||
signature = @websub.sign(secret || "", xml)
|
||||
Logger.debug(fn -> "Pushing #{topic} to #{callback}" end)
|
||||
|
@ -102,7 +129,7 @@ def maybe_start_job(running_jobs, queue) do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_cast({:enqueue, type, payload, priority}, state) when type in [:incoming_doc] do
|
||||
def handle_cast({:enqueue, type, payload, priority}, state) when type in [:incoming_doc, :incoming_ap_doc] do
|
||||
%{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state
|
||||
i_queue = enqueue_sorted(i_queue, {type, payload}, 1)
|
||||
{i_running_jobs, i_queue} = maybe_start_job(i_running_jobs, i_queue)
|
||||
|
@ -139,4 +166,13 @@ def enqueue_sorted(queue, element, priority) do
|
|||
def queue_pop([%{item: element} | queue]) do
|
||||
{element, queue}
|
||||
end
|
||||
|
||||
def ap_enabled_actor(id) do
|
||||
user = User.get_by_ap_id(id)
|
||||
if User.ap_enabled?(user) do
|
||||
{:ok, user}
|
||||
else
|
||||
ActivityPub.make_user_from_ap_id(id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
79
lib/pleroma/web/http_signatures/http_signatures.ex
Normal file
79
lib/pleroma/web/http_signatures/http_signatures.ex
Normal file
|
@ -0,0 +1,79 @@
|
|||
# https://tools.ietf.org/html/draft-cavage-http-signatures-08
|
||||
defmodule Pleroma.Web.HTTPSignatures do
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
require Logger
|
||||
|
||||
def split_signature(sig) do
|
||||
default = %{"headers" => "date"}
|
||||
|
||||
sig = sig
|
||||
|> String.trim()
|
||||
|> String.split(",")
|
||||
|> Enum.reduce(default, fn(part, acc) ->
|
||||
[key | rest] = String.split(part, "=")
|
||||
value = Enum.join(rest, "=")
|
||||
Map.put(acc, key, String.trim(value, "\""))
|
||||
end)
|
||||
|
||||
Map.put(sig, "headers", String.split(sig["headers"], ~r/\s/))
|
||||
end
|
||||
|
||||
def validate(headers, signature, public_key) do
|
||||
sigstring = build_signing_string(headers, signature["headers"])
|
||||
{:ok, sig} = Base.decode64(signature["signature"])
|
||||
:public_key.verify(sigstring, :sha256, sig, public_key)
|
||||
end
|
||||
|
||||
def validate_conn(conn) do
|
||||
# TODO: How to get the right key and see if it is actually valid for that request.
|
||||
# For now, fetch the key for the actor.
|
||||
with actor_id <- conn.params["actor"],
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||
if validate_conn(conn, public_key) do
|
||||
true
|
||||
else
|
||||
Logger.debug("Could not validate, re-fetching user and trying one more time.")
|
||||
# Fetch user anew and try one more time
|
||||
with actor_id <- conn.params["actor"],
|
||||
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||
validate_conn(conn, public_key)
|
||||
end
|
||||
end
|
||||
else
|
||||
e ->
|
||||
Logger.debug("Could not public key!")
|
||||
end
|
||||
end
|
||||
|
||||
def validate_conn(conn, public_key) do
|
||||
headers = Enum.into(conn.req_headers, %{})
|
||||
signature = split_signature(headers["signature"])
|
||||
validate(headers, signature, public_key)
|
||||
end
|
||||
|
||||
def build_signing_string(headers, used_headers) do
|
||||
used_headers
|
||||
|> Enum.map(fn (header) -> "#{header}: #{headers[header]}" end)
|
||||
|> Enum.join("\n")
|
||||
end
|
||||
|
||||
def sign(user, headers) do
|
||||
with {:ok, %{info: %{"keys" => keys}}} <- Pleroma.Web.WebFinger.ensure_keys_present(user),
|
||||
{:ok, private_key, _} = Pleroma.Web.Salmon.keys_from_pem(keys) do
|
||||
sigstring = build_signing_string(headers, Map.keys(headers))
|
||||
signature = :public_key.sign(sigstring, :sha256, private_key)
|
||||
|> Base.encode64()
|
||||
|
||||
[
|
||||
keyId: user.ap_id <> "#main-key",
|
||||
algorithm: "rsa-sha256",
|
||||
headers: Map.keys(headers) |> Enum.join(" "),
|
||||
signature: signature
|
||||
]
|
||||
|> Enum.map(fn({k, v}) -> "#{k}=\"#{v}\"" end)
|
||||
|> Enum.join(",")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -24,6 +24,7 @@ def create_app(conn, params) do
|
|||
end
|
||||
|
||||
def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
||||
original_user = user
|
||||
params = if bio = params["note"] do
|
||||
Map.put(params, "bio", bio)
|
||||
else
|
||||
|
@ -40,7 +41,7 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
|||
with %Plug.Upload{} <- avatar,
|
||||
{:ok, object} <- ActivityPub.upload(avatar),
|
||||
change = Ecto.Changeset.change(user, %{avatar: object.data}),
|
||||
{:ok, user} = Repo.update(change) do
|
||||
{:ok, user} = User.update_and_set_cache(change) do
|
||||
user
|
||||
else
|
||||
_e -> user
|
||||
|
@ -54,7 +55,7 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
|||
{: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
|
||||
{:ok, user} <- User.update_and_set_cache(change) do
|
||||
user
|
||||
else
|
||||
_e -> user
|
||||
|
@ -64,7 +65,10 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
|||
end
|
||||
|
||||
with changeset <- User.update_changeset(user, params),
|
||||
{:ok, user} <- Repo.update(changeset) do
|
||||
{:ok, user} <- User.update_and_set_cache(changeset) do
|
||||
if original_user != user do
|
||||
CommonAPI.update(user)
|
||||
end
|
||||
json conn, AccountView.render("account.json", %{user: user})
|
||||
else
|
||||
_e ->
|
||||
|
@ -150,6 +154,7 @@ def home_timeline(%{assigns: %{user: user}} = conn, params) do
|
|||
params = params
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("user", user)
|
||||
|
||||
activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||
|> Enum.reverse
|
||||
|
@ -181,7 +186,7 @@ def user_statuses(%{assigns: %{user: user}} = conn, params) do
|
|||
|> Map.put("actor_id", ap_id)
|
||||
|> Map.put("whole_db", true)
|
||||
|
||||
activities = ActivityPub.fetch_activities([], params)
|
||||
activities = ActivityPub.fetch_public_activities(params)
|
||||
|> Enum.reverse
|
||||
|
||||
render conn, StatusView, "index.json", %{activities: activities, for: user, as: :activity}
|
||||
|
@ -189,14 +194,15 @@ def user_statuses(%{assigns: %{user: user}} = conn, params) do
|
|||
end
|
||||
|
||||
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Activity{} = activity <- Repo.get(Activity, id) do
|
||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||
true <- ActivityPub.visible_for_user?(activity, user) do
|
||||
render conn, StatusView, "status.json", %{activity: activity, for: user}
|
||||
end
|
||||
end
|
||||
|
||||
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"], %{"blocking_user" => user}),
|
||||
activities <- ActivityPub.fetch_activities_for_context(activity.data["context"], %{"blocking_user" => user, "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
|
||||
|
@ -463,12 +469,12 @@ def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) d
|
|||
end
|
||||
|
||||
def favourites(%{assigns: %{user: user}} = conn, _) do
|
||||
params = conn
|
||||
params = %{}
|
||||
|> Map.put("type", "Create")
|
||||
|> Map.put("favorited_by", user.ap_id)
|
||||
|> Map.put("blocking_user", user)
|
||||
|
||||
activities = ActivityPub.fetch_activities([], params)
|
||||
activities = ActivityPub.fetch_public_activities(params)
|
||||
|> Enum.reverse
|
||||
|
||||
conn
|
||||
|
|
|
@ -25,7 +25,6 @@ def connect(params, socket) do
|
|||
def id(_), do: nil
|
||||
|
||||
def handle(:text, message, _state) do
|
||||
IO.inspect message
|
||||
#| :ok
|
||||
#| state
|
||||
#| {:text, message}
|
||||
|
|
|
@ -18,7 +18,7 @@ def render("account.json", %{user: user}) do
|
|||
id: to_string(user.id),
|
||||
username: hd(String.split(user.nickname, "@")),
|
||||
acct: user.nickname,
|
||||
display_name: user.name,
|
||||
display_name: user.name || user.nickname,
|
||||
locked: false,
|
||||
created_at: Utils.to_masto_date(user.inserted_at),
|
||||
followers_count: user_info.follower_count,
|
||||
|
|
|
@ -58,7 +58,7 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
|||
announcement_count = object["announcement_count"] || 0
|
||||
|
||||
tags = object["tag"] || []
|
||||
sensitive = Enum.member?(tags, "nsfw")
|
||||
sensitive = object["sensitive"] || Enum.member?(tags, "nsfw")
|
||||
|
||||
mentions = activity.data["to"]
|
||||
|> Enum.map(fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end)
|
||||
|
@ -96,7 +96,7 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
|||
muted: false,
|
||||
sensitive: sensitive,
|
||||
spoiler_text: object["summary"] || "",
|
||||
visibility: "public",
|
||||
visibility: get_visibility(object),
|
||||
media_attachments: attachments |> Enum.take(4),
|
||||
mentions: mentions,
|
||||
tags: [], # fix,
|
||||
|
@ -109,6 +109,18 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
|||
}
|
||||
end
|
||||
|
||||
def get_visibility(object) do
|
||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||
to = object["to"] || []
|
||||
cc = object["cc"] || []
|
||||
cond do
|
||||
public in to -> "public"
|
||||
public in cc -> "unlisted"
|
||||
Enum.any?(to, &(String.contains?(&1, "/followers"))) -> "private"
|
||||
true -> "direct"
|
||||
end
|
||||
end
|
||||
|
||||
def render("attachment.json", %{attachment: attachment}) do
|
||||
[%{"mediaType" => media_type, "href" => href} | _] = attachment["url"]
|
||||
|
||||
|
|
|
@ -76,10 +76,17 @@ def to_simple_form(%{data: %{"object" => %{"type" => "Note"}}} = activity, user,
|
|||
|
||||
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
|
||||
mentions = activity.recipients |> get_mentions
|
||||
|
||||
categories = (activity.data["object"]["tag"] || [])
|
||||
|> Enum.map(fn (tag) -> {:category, [term: to_charlist(tag)], []} end)
|
||||
|> Enum.map(fn (tag) ->
|
||||
if is_binary(tag) do
|
||||
{:category, [term: to_charlist(tag)], []}
|
||||
else
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(&(&1))
|
||||
|
||||
emoji_links = get_emoji_links(activity.data["object"]["emoji"] || %{})
|
||||
|
||||
|
@ -110,7 +117,7 @@ def to_simple_form(%{data: %{"type" => "Like"}} = activity, user, with_author) d
|
|||
|
||||
_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
|
||||
mentions = activity.recipients |> get_mentions
|
||||
|
||||
[
|
||||
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/favorite']},
|
||||
|
@ -144,7 +151,7 @@ def to_simple_form(%{data: %{"type" => "Announce"}} = activity, user, with_autho
|
|||
|
||||
retweeted_xml = to_simple_form(retweeted_activity, retweeted_user, true)
|
||||
|
||||
mentions = activity.data["to"] |> get_mentions
|
||||
mentions = activity.recipients |> get_mentions
|
||||
[
|
||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
||||
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/share']},
|
||||
|
@ -168,7 +175,7 @@ def to_simple_form(%{data: %{"type" => "Follow"}} = activity, user, with_author)
|
|||
|
||||
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
||||
|
||||
mentions = (activity.data["to"] || []) |> get_mentions
|
||||
mentions = (activity.recipients || []) |> get_mentions
|
||||
[
|
||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
||||
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/follow']},
|
||||
|
@ -196,7 +203,7 @@ def to_simple_form(%{data: %{"type" => "Undo"}} = activity, user, with_author) d
|
|||
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
||||
follow_activity = Activity.get_by_ap_id(activity.data["object"])
|
||||
|
||||
mentions = (activity.data["to"] || []) |> get_mentions
|
||||
mentions = (activity.recipients || []) |> get_mentions
|
||||
[
|
||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
||||
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/unfollow']},
|
||||
|
|
|
@ -88,6 +88,7 @@ def fetch_replied_to_activity(entry, inReplyTo) do
|
|||
end
|
||||
end
|
||||
|
||||
# TODO: Clean this up a bit.
|
||||
def handle_note(entry, doc \\ nil) do
|
||||
with id <- XML.string_from_xpath("//id", entry),
|
||||
activity when is_nil(activity) <- Activity.get_create_activity_by_object_ap_id(id),
|
||||
|
@ -104,15 +105,18 @@ def handle_note(entry, doc \\ nil) do
|
|||
mentions <- get_mentions(entry),
|
||||
to <- make_to_list(actor, mentions),
|
||||
date <- XML.string_from_xpath("//published", entry),
|
||||
unlisted <- XML.string_from_xpath("//mastodon:scope", entry) == "unlisted",
|
||||
cc <- if(unlisted, do: ["https://www.w3.org/ns/activitystreams#Public"], else: []),
|
||||
note <- CommonAPI.Utils.make_note_data(actor.ap_id, to, context, content_html, attachments, inReplyToActivity, [], cw),
|
||||
note <- note |> Map.put("id", id) |> Map.put("tag", tags),
|
||||
note <- note |> Map.put("published", date),
|
||||
note <- note |> Map.put("emoji", get_emoji(entry)),
|
||||
note <- add_external_url(note, entry),
|
||||
note <- note |> Map.put("cc", cc),
|
||||
# TODO: Handle this case in make_note_data
|
||||
note <- (if inReplyTo && !inReplyToActivity, do: note |> Map.put("inReplyTo", inReplyTo), else: note)
|
||||
do
|
||||
res = ActivityPub.create(to, actor, context, note, %{}, date, false)
|
||||
res = ActivityPub.create(%{to: to, actor: actor, context: context, object: note, published: date, local: false, additional: %{"cc" => cc}})
|
||||
User.increase_note_count(actor)
|
||||
res
|
||||
else
|
||||
|
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.OStatus do
|
|||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.{WebFinger, Websub}
|
||||
alias Pleroma.Web.OStatus.{FollowHandler, NoteHandler, DeleteHandler}
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
def feed_path(user) do
|
||||
"#{user.ap_id}/feed.atom"
|
||||
|
@ -177,6 +178,13 @@ def get_tags(entry) do
|
|||
end
|
||||
|
||||
def maybe_update(doc, user) do
|
||||
if "true" == string_from_xpath("//author[1]/ap_enabled", doc) do
|
||||
Transmogrifier.upgrade_user_from_ap_id(user.ap_id)
|
||||
else
|
||||
maybe_update_ostatus(doc, user)
|
||||
end
|
||||
end
|
||||
def maybe_update_ostatus(doc, user) do
|
||||
old_data = %{
|
||||
avatar: user.avatar,
|
||||
bio: user.bio,
|
||||
|
@ -218,11 +226,6 @@ def find_or_make_user(uri) do
|
|||
end
|
||||
end
|
||||
|
||||
def insert_or_update_user(data) do
|
||||
cs = User.remote_user_creation(data)
|
||||
Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
|
||||
end
|
||||
|
||||
def make_user(uri, update \\ false) do
|
||||
with {:ok, info} <- gather_user_info(uri) do
|
||||
data = %{
|
||||
|
@ -236,7 +239,7 @@ def make_user(uri, update \\ false) do
|
|||
with false <- update,
|
||||
%User{} = user <- User.get_by_ap_id(data.ap_id) do
|
||||
{:ok, user}
|
||||
else _e -> insert_or_update_user(data)
|
||||
else _e -> User.insert_or_update_user(data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -297,7 +300,10 @@ def fetch_activity_from_atom_url(url) do
|
|||
with {:ok, %{body: body, status_code: code}} when code in 200..299 <- @httpoison.get(url, [Accept: "application/atom+xml"], follow_redirect: true, timeout: 10000, recv_timeout: 20000) do
|
||||
Logger.debug("Got document from #{url}, handling...")
|
||||
handle_incoming(body)
|
||||
else e -> Logger.debug("Couldn't get #{url}: #{inspect(e)}")
|
||||
else
|
||||
e ->
|
||||
Logger.debug("Couldn't get #{url}: #{inspect(e)}")
|
||||
e
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -306,7 +312,10 @@ def fetch_activity_from_html_url(url) do
|
|||
with {:ok, %{body: body}} <- @httpoison.get(url, [], follow_redirect: true, timeout: 10000, recv_timeout: 20000),
|
||||
{:ok, atom_url} <- get_atom_url(body) do
|
||||
fetch_activity_from_atom_url(atom_url)
|
||||
else e -> Logger.debug("Couldn't get #{url}: #{inspect(e)}")
|
||||
else
|
||||
e ->
|
||||
Logger.debug("Couldn't get #{url}: #{inspect(e)}")
|
||||
e
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -6,27 +6,25 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
|||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.{OStatus, Federator}
|
||||
alias Pleroma.Web.XML
|
||||
alias Pleroma.Web.ActivityPub.ActivityPubController
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
import Ecto.Query
|
||||
|
||||
def feed_redirect(conn, %{"nickname" => nickname}) do
|
||||
def feed_redirect(conn, %{"nickname" => nickname} = params) do
|
||||
user = User.get_cached_by_nickname(nickname)
|
||||
|
||||
case get_format(conn) do
|
||||
"html" -> Fallback.RedirectController.redirector(conn, nil)
|
||||
"activity+json" -> ActivityPubController.user(conn, params)
|
||||
_ -> redirect conn, external: OStatus.feed_path(user)
|
||||
end
|
||||
end
|
||||
|
||||
def feed(conn, %{"nickname" => nickname} = params) do
|
||||
user = User.get_cached_by_nickname(nickname)
|
||||
query = from activity in Activity,
|
||||
where: fragment("?->>'actor' = ?", activity.data, ^user.ap_id),
|
||||
limit: 20,
|
||||
order_by: [desc: :id]
|
||||
|
||||
activities = query
|
||||
|> restrict_max(params)
|
||||
|> Repo.all
|
||||
activities = ActivityPub.fetch_public_activities(%{"whole_db" => true, "actor_id" => user.ap_id})
|
||||
|> Enum.reverse
|
||||
|
||||
response = user
|
||||
|> FeedRepresenter.to_simple_form(activities, [user])
|
||||
|
@ -55,11 +53,6 @@ defp decode_or_retry(body) do
|
|||
end
|
||||
end
|
||||
|
||||
defp restrict_max(query, %{"max_id" => max_id}) do
|
||||
from activity in query, where: activity.id < ^max_id
|
||||
end
|
||||
defp restrict_max(query, _), do: query
|
||||
|
||||
def salmon_incoming(conn, _) do
|
||||
{:ok, body, _conn} = read_body(conn)
|
||||
{:ok, doc} = decode_or_retry(body)
|
||||
|
@ -70,17 +63,23 @@ def salmon_incoming(conn, _) do
|
|||
|> send_resp(200, "")
|
||||
end
|
||||
|
||||
def object(conn, %{"uuid" => uuid}) do
|
||||
with id <- o_status_url(conn, :object, uuid),
|
||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id),
|
||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||
case get_format(conn) do
|
||||
"html" -> redirect(conn, to: "/notice/#{activity.id}")
|
||||
_ -> represent_activity(conn, activity, user)
|
||||
# TODO: Data leak
|
||||
def object(conn, %{"uuid" => uuid} = params) do
|
||||
if get_format(conn) == "activity+json" do
|
||||
ActivityPubController.object(conn, params)
|
||||
else
|
||||
with id <- o_status_url(conn, :object, uuid),
|
||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id),
|
||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||
case get_format(conn) do
|
||||
"html" -> redirect(conn, to: "/notice/#{activity.id}")
|
||||
_ -> represent_activity(conn, activity, user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Data leak
|
||||
def activity(conn, %{"uuid" => uuid}) do
|
||||
with id <- o_status_url(conn, :activity, uuid),
|
||||
%Activity{} = activity <- Activity.get_by_ap_id(id),
|
||||
|
@ -92,6 +91,7 @@ def activity(conn, %{"uuid" => uuid}) do
|
|||
end
|
||||
end
|
||||
|
||||
# TODO: Data leak
|
||||
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
|
||||
|
|
|
@ -12,6 +12,12 @@ def to_simple_form(user) do
|
|||
[]
|
||||
end
|
||||
|
||||
ap_enabled = if user.local do
|
||||
[{:ap_enabled, ['true']}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
[
|
||||
{:id, [ap_id]},
|
||||
{:"activity:object", ['http://activitystrea.ms/schema/1.0/person']},
|
||||
|
@ -22,6 +28,6 @@ def to_simple_form(user) do
|
|||
{:summary, [bio]},
|
||||
{:name, [nickname]},
|
||||
{:link, [rel: 'avatar', href: avatar_url], []}
|
||||
] ++ banner
|
||||
] ++ banner ++ ap_enabled
|
||||
end
|
||||
end
|
||||
|
|
|
@ -222,7 +222,7 @@ def user_fetcher(username) do
|
|||
end
|
||||
|
||||
pipeline :ostatus do
|
||||
plug :accepts, ["xml", "atom", "html"]
|
||||
plug :accepts, ["xml", "atom", "html", "activity+json"]
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web do
|
||||
|
@ -243,7 +243,18 @@ def user_fetcher(username) do
|
|||
|
||||
end
|
||||
|
||||
pipeline :activitypub do
|
||||
plug :accepts, ["activity+json"]
|
||||
plug Pleroma.Web.Plugs.HTTPSignaturePlug
|
||||
end
|
||||
|
||||
if @federating do
|
||||
scope "/", Pleroma.Web.ActivityPub do
|
||||
pipe_through :activitypub
|
||||
post "/users/:nickname/inbox", ActivityPubController, :inbox
|
||||
post "/inbox", ActivityPubController, :inbox
|
||||
end
|
||||
|
||||
scope "/.well-known", Pleroma.Web do
|
||||
pipe_through :well_known
|
||||
|
||||
|
|
|
@ -29,7 +29,8 @@ def fetch_magic_key(salmon) do
|
|||
with [data, _, _, _, _] <- decode(salmon),
|
||||
doc <- XML.parse_document(data),
|
||||
uri when not is_nil(uri) <- XML.string_from_xpath("/entry/author[1]/uri", doc),
|
||||
{:ok, %{info: %{"magic_key" => magic_key}}} <- Pleroma.Web.OStatus.find_or_make_user(uri) do
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(uri),
|
||||
magic_key <- encode_key(public_key) do
|
||||
{:ok, magic_key}
|
||||
end
|
||||
end
|
||||
|
@ -138,7 +139,8 @@ def encode(private_key, doc) do
|
|||
{:ok, salmon}
|
||||
end
|
||||
|
||||
def remote_users(%{data: %{"to" => to}}) do
|
||||
def remote_users(%{data: %{"to" => to} = data}) do
|
||||
to = to ++ (data["cc"] || [])
|
||||
to
|
||||
|> Enum.map(fn(id) -> User.get_cached_by_ap_id(id) end)
|
||||
|> Enum.filter(fn(user) -> user && !user.local end)
|
||||
|
@ -154,8 +156,16 @@ defp send_to_user(%{info: %{"salmon" => salmon}}, feed, poster) do
|
|||
|
||||
defp send_to_user(_,_,_), do: nil
|
||||
|
||||
@supported_activities [
|
||||
"Create",
|
||||
"Follow",
|
||||
"Like",
|
||||
"Announce",
|
||||
"Undo",
|
||||
"Delete"
|
||||
]
|
||||
def publish(user, activity, poster \\ &@httpoison.post/4)
|
||||
def publish(%{info: %{"keys" => keys}} = user, activity, poster) do
|
||||
def publish(%{info: %{"keys" => keys}} = user, %{data: %{"type" => type}} = activity, poster) when type in @supported_activities do
|
||||
feed = ActivityRepresenter.to_simple_form(activity, user, true)
|
||||
|> ActivityRepresenter.wrap_with_entry
|
||||
|> :xmerl.export_simple(:xmerl_xml)
|
||||
|
|
|
@ -74,7 +74,6 @@ def handle_cast(%{action: :add, topic: topic, socket: socket}, sockets) do
|
|||
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
|
||||
|
||||
|
@ -84,12 +83,11 @@ def handle_cast(%{action: :remove, topic: topic, socket: socket}, sockets) do
|
|||
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)}")
|
||||
Logger.info("Unknown: #{inspect(m)}, #{inspect(state)}")
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
|
|
|
@ -56,7 +56,8 @@ def to_map(%Activity{data: %{"type" => "Like", "published" => created_at}} = act
|
|||
}
|
||||
end
|
||||
|
||||
def to_map(%Activity{data: %{"type" => "Follow", "published" => created_at, "object" => followed_id}} = activity, %{user: user} = opts) do
|
||||
def to_map(%Activity{data: %{"type" => "Follow", "object" => followed_id}} = activity, %{user: user} = opts) do
|
||||
created_at = activity.data["published"] || (DateTime.to_iso8601(activity.inserted_at))
|
||||
created_at = created_at |> Utils.date_to_asctime
|
||||
|
||||
followed = User.get_cached_by_ap_id(followed_id)
|
||||
|
@ -125,7 +126,7 @@ def to_map(%Activity{data: %{"object" => %{"content" => content} = object}} = ac
|
|||
|
||||
mentions = opts[:mentioned] || []
|
||||
|
||||
attentions = activity.data["to"]
|
||||
attentions = activity.recipients
|
||||
|> Enum.map(fn (ap_id) -> Enum.find(mentions, fn(user) -> ap_id == user.ap_id end) end)
|
||||
|> Enum.filter(&(&1))
|
||||
|> Enum.map(fn (user) -> UserView.render("show.json", %{user: user, for: opts[:for]}) end)
|
||||
|
@ -133,7 +134,9 @@ def to_map(%Activity{data: %{"object" => %{"content" => content} = object}} = ac
|
|||
conversation_id = conversation_id(activity)
|
||||
|
||||
tags = activity.data["object"]["tag"] || []
|
||||
possibly_sensitive = Enum.member?(tags, "nsfw")
|
||||
possibly_sensitive = activity.data["object"]["sensitive"] || Enum.member?(tags, "nsfw")
|
||||
|
||||
tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags
|
||||
|
||||
summary = activity.data["object"]["summary"]
|
||||
content = if !!summary and summary != "" do
|
||||
|
@ -161,7 +164,7 @@ def to_map(%Activity{data: %{"object" => %{"content" => content} = object}} = ac
|
|||
"repeat_num" => announcement_count,
|
||||
"favorited" => to_boolean(favorited),
|
||||
"repeated" => to_boolean(repeated),
|
||||
"external_url" => object["external_url"],
|
||||
"external_url" => object["external_url"] || object["id"],
|
||||
"tags" => tags,
|
||||
"activity_type" => "post",
|
||||
"possibly_sensitive" => possibly_sensitive
|
||||
|
|
|
@ -2,9 +2,8 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter do
|
|||
use Pleroma.Web.TwitterAPI.Representers.BaseRepresenter
|
||||
alias Pleroma.Object
|
||||
|
||||
def to_map(%Object{} = object, _opts) do
|
||||
def to_map(%Object{data: %{"url" => [url | _]}} = object, _opts) do
|
||||
data = object.data
|
||||
url = List.first(data["url"])
|
||||
%{
|
||||
url: url["href"] |> Pleroma.Web.MediaProxy.url(),
|
||||
mimetype: url["mediaType"],
|
||||
|
@ -13,6 +12,19 @@ def to_map(%Object{} = object, _opts) do
|
|||
}
|
||||
end
|
||||
|
||||
def to_map(%Object{data: %{"url" => url} = data}, _opts) when is_binary(url) do
|
||||
%{
|
||||
url: url |> Pleroma.Web.MediaProxy.url(),
|
||||
mimetype: data["mediaType"],
|
||||
id: data["uuid"],
|
||||
oembed: false
|
||||
}
|
||||
end
|
||||
|
||||
def to_map(%Object{}, _opts) do
|
||||
%{}
|
||||
end
|
||||
|
||||
# If we only get the naked data, wrap in an object
|
||||
def to_map(%{} = data, opts) do
|
||||
to_map(%Object{data: data}, opts)
|
||||
|
|
|
@ -13,26 +13,38 @@ def create_status(%User{} = user, %{"status" => _} = data) do
|
|||
end
|
||||
|
||||
def fetch_friend_statuses(user, opts \\ %{}) do
|
||||
opts = Map.put(opts, "blocking_user", user)
|
||||
opts = opts
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> Map.put("type", ["Create", "Announce", "Follow", "Like"])
|
||||
|
||||
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)
|
||||
opts = opts
|
||||
|> Map.put("local_only", true)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("type", ["Create", "Announce", "Follow"])
|
||||
|
||||
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)
|
||||
opts = opts
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("type", ["Create", "Announce", "Follow"])
|
||||
|
||||
ActivityPub.fetch_public_activities(opts)
|
||||
|> activities_to_statuses(%{for: user})
|
||||
end
|
||||
|
||||
def fetch_user_statuses(user, opts \\ %{}) do
|
||||
ActivityPub.fetch_activities([], opts)
|
||||
opts = opts
|
||||
|> Map.put("type", ["Create"])
|
||||
ActivityPub.fetch_public_activities(opts)
|
||||
|> activities_to_statuses(%{for: user})
|
||||
end
|
||||
|
||||
|
@ -43,7 +55,7 @@ def fetch_mentions(user, opts \\ %{}) 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, %{"blocking_user" => user}),
|
||||
activities <- ActivityPub.fetch_activities_for_context(context, %{"blocking_user" => user, "user" => user}),
|
||||
statuses <- activities |> activities_to_statuses(%{for: user})
|
||||
do
|
||||
statuses
|
||||
|
@ -53,7 +65,8 @@ def fetch_conversation(user, id) do
|
|||
end
|
||||
|
||||
def fetch_status(user, id) do
|
||||
with %Activity{} = activity <- Repo.get(Activity, id) do
|
||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||
true <- ActivityPub.visible_for_user?(activity, user) do
|
||||
activity_to_status(activity, %{for: user})
|
||||
end
|
||||
end
|
||||
|
@ -276,7 +289,7 @@ defp activity_to_status(activity, opts) do
|
|||
actor = get_in(activity.data, ["actor"])
|
||||
user = User.get_cached_by_ap_id(actor)
|
||||
# mentioned_users = Repo.all(from user in User, where: user.ap_id in ^activity.data["to"])
|
||||
mentioned_users = Enum.map(activity.data["to"] || [], fn (ap_id) ->
|
||||
mentioned_users = Enum.map(activity.recipients || [], fn (ap_id) ->
|
||||
if ap_id do
|
||||
User.get_cached_by_ap_id(ap_id)
|
||||
else
|
||||
|
|
|
@ -207,7 +207,8 @@ def register(conn, params) do
|
|||
def update_avatar(%{assigns: %{user: user}} = conn, params) do
|
||||
{:ok, object} = ActivityPub.upload(params)
|
||||
change = Changeset.change(user, %{avatar: object.data})
|
||||
{:ok, user} = Repo.update(change)
|
||||
{:ok, user} = User.update_and_set_cache(change)
|
||||
CommonAPI.update(user)
|
||||
|
||||
render(conn, UserView, "show.json", %{user: user, for: user})
|
||||
end
|
||||
|
@ -216,7 +217,8 @@ def update_banner(%{assigns: %{user: user}} = conn, params) 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
|
||||
{:ok, user} <- User.update_and_set_cache(change) do
|
||||
CommonAPI.update(user)
|
||||
%{"url" => [ %{ "href" => href } | _ ]} = object.data
|
||||
response = %{ url: href } |> Poison.encode!
|
||||
conn
|
||||
|
@ -228,7 +230,7 @@ def update_background(%{assigns: %{user: user}} = conn, params) 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
|
||||
{:ok, _user} <- User.update_and_set_cache(change) do
|
||||
%{"url" => [ %{ "href" => href } | _ ]} = object.data
|
||||
response = %{ url: href } |> Poison.encode!
|
||||
conn
|
||||
|
@ -255,7 +257,7 @@ def update_most_recent_notification(%{assigns: %{user: user}} = conn, %{"id" =>
|
|||
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} <- User.update_and_set_cache(changeset) do
|
||||
conn
|
||||
|> json_reply(200, Poison.encode!(mrn))
|
||||
else
|
||||
|
@ -305,7 +307,8 @@ def update_profile(%{assigns: %{user: user}} = conn, params) do
|
|||
end
|
||||
|
||||
with changeset <- User.update_changeset(user, params),
|
||||
{:ok, user} <- Repo.update(changeset) do
|
||||
{:ok, user} <- User.update_and_set_cache(changeset) do
|
||||
CommonAPI.update(user)
|
||||
render(conn, UserView, "user.json", %{user: user, for: user})
|
||||
else
|
||||
error ->
|
||||
|
|
|
@ -45,6 +45,7 @@ def represent_user(user) do
|
|||
{:Link, %{rel: "http://webfinger.net/rel/profile-page", type: "text/html", href: user.ap_id}},
|
||||
{:Link, %{rel: "salmon", href: OStatus.salmon_path(user)}},
|
||||
{:Link, %{rel: "magic-public-key", href: "data:application/magic-public-key,#{magic_key}"}},
|
||||
{:Link, %{rel: "self", type: "application/activity+json", href: user.ap_id}},
|
||||
{:Link, %{rel: "http://ostatus.org/schema/1.0/subscribe", template: OStatus.remote_follow_path()}}
|
||||
]
|
||||
}
|
||||
|
@ -59,7 +60,8 @@ def ensure_keys_present(user) do
|
|||
else
|
||||
{:ok, pem} = Salmon.generate_rsa_pem
|
||||
info = Map.put(info, "keys", pem)
|
||||
Repo.update(Ecto.Changeset.change(user, info: info))
|
||||
Ecto.Changeset.change(user, info: info)
|
||||
|> User.update_and_set_cache()
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -70,12 +72,14 @@ defp webfinger_from_xml(doc) do
|
|||
subject = XML.string_from_xpath("//Subject", doc)
|
||||
salmon = XML.string_from_xpath(~s{//Link[@rel="salmon"]/@href}, doc)
|
||||
subscribe_address = XML.string_from_xpath(~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template}, doc)
|
||||
ap_id = XML.string_from_xpath(~s{//Link[@rel="self" and @type="application/activity+json"]/@href}, doc)
|
||||
data = %{
|
||||
"magic_key" => magic_key,
|
||||
"topic" => topic,
|
||||
"subject" => subject,
|
||||
"salmon" => salmon,
|
||||
"subscribe_address" => subscribe_address
|
||||
"subscribe_address" => subscribe_address,
|
||||
"ap_id" => ap_id
|
||||
}
|
||||
{:ok, data}
|
||||
end
|
||||
|
@ -102,6 +106,7 @@ def find_lrdd_template(domain) do
|
|||
end
|
||||
|
||||
def finger(account) do
|
||||
account = String.trim_leading(account, "@")
|
||||
domain = with [_name, domain] <- String.split(account, "@") do
|
||||
domain
|
||||
else _e ->
|
||||
|
|
|
@ -38,7 +38,15 @@ def verify(subscription, getter \\ &@httpoison.get/3) do
|
|||
end
|
||||
end
|
||||
|
||||
def publish(topic, user, activity) do
|
||||
@supported_activities [
|
||||
"Create",
|
||||
"Follow",
|
||||
"Like",
|
||||
"Announce",
|
||||
"Undo",
|
||||
"Delete"
|
||||
]
|
||||
def publish(topic, user, %{data: %{"type" => type}} = activity) when type in @supported_activities do
|
||||
# TODO: Only send to still valid subscriptions.
|
||||
query = from sub in WebsubServerSubscription,
|
||||
where: sub.topic == ^topic and sub.state == "active"
|
||||
|
@ -58,6 +66,7 @@ def publish(topic, user, activity) do
|
|||
Pleroma.Web.Federator.enqueue(:publish_single_websub, data)
|
||||
end)
|
||||
end
|
||||
def publish(_,_,_), do: ""
|
||||
|
||||
def sign(secret, doc) do
|
||||
:crypto.hmac(:sha, secret, to_string(doc)) |> Base.encode16 |> String.downcase
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddRecipientsToActivities do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:activities) do
|
||||
add :recipients, {:array, :string}
|
||||
end
|
||||
|
||||
create index(:activities, [:recipients], using: :gin)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,21 @@
|
|||
defmodule Pleroma.Repo.Migrations.FillRecipientsInActivities do
|
||||
use Ecto.Migration
|
||||
alias Pleroma.{Repo, Activity}
|
||||
|
||||
def up do
|
||||
max = Repo.aggregate(Activity, :max, :id)
|
||||
if max do
|
||||
IO.puts("#{max} activities")
|
||||
chunks = 0..(round(max / 10_000))
|
||||
|
||||
Enum.each(chunks, fn (i) ->
|
||||
min = i * 10_000
|
||||
max = min + 10_000
|
||||
execute("""
|
||||
update activities set recipients = array(select jsonb_array_elements_text(data->'to')) where id > #{min} and id <= #{max};
|
||||
""")
|
||||
|> IO.inspect
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
defmodule Pleroma.Repo.Migrations.MakeFollowingPostgresArray do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:users) do
|
||||
add :following_temp, {:array, :string}
|
||||
end
|
||||
|
||||
execute """
|
||||
update users set following_temp = array(select jsonb_array_elements_text(following));
|
||||
"""
|
||||
|
||||
alter table(:users) do
|
||||
remove :following
|
||||
end
|
||||
rename table(:users), :following_temp, to: :following
|
||||
end
|
||||
end
|
1
test/fixtures/avatar_data_uri
vendored
Normal file
1
test/fixtures/avatar_data_uri
vendored
Normal file
File diff suppressed because one or more lines are too long
44
test/fixtures/httpoison_mock/7369654.atom
vendored
Normal file
44
test/fixtures/httpoison_mock/7369654.atom
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:georss="http://www.georss.org/georss" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:statusnet="http://status.net/schema/api/1/">
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||
<id>tag:shitposter.club,2018-02-22:noticeId=7369654:objectType=comment</id>
|
||||
<title>New comment by shpuld</title>
|
||||
<content type="html">@<a href="https://testing.pleroma.lol/users/lain" class="h-card mention" title="Rael Electric Razor">lain</a> me far right</content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7369654"/>
|
||||
<status_net notice_id="7369654"></status_net>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<published>2018-02-22T09:20:12+00:00</published>
|
||||
<updated>2018-02-22T09:20:12+00:00</updated>
|
||||
<author>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
|
||||
<uri>https://shitposter.club/user/5381</uri>
|
||||
<name>shpuld</name>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/shpuld"/>
|
||||
<link rel="avatar" type="image/png" media:width="864" media:height="864" href="https://shitposter.club/avatar/5381-original-20171230093854.png"/>
|
||||
<link rel="avatar" type="image/png" media:width="96" media:height="96" href="https://shitposter.club/avatar/5381-96-20171230093854.png"/>
|
||||
<link rel="avatar" type="image/png" media:width="48" media:height="48" href="https://shitposter.club/avatar/5381-48-20171230093854.png"/>
|
||||
<link rel="avatar" type="image/png" media:width="24" media:height="24" href="https://shitposter.club/avatar/5381-24-20171230093900.png"/>
|
||||
<poco:preferredUsername>shpuld</poco:preferredUsername>
|
||||
<poco:displayName>shp</poco:displayName>
|
||||
<followers url="https://shitposter.club/shpuld/subscribers"></followers>
|
||||
<statusnet:profile_info local_id="5381"></statusnet:profile_info>
|
||||
</author>
|
||||
<thr:in-reply-to ref="https://testing.pleroma.lol/objects/b319022a-4946-44c5-9de9-34801f95507b" href="https://testing.pleroma.lol/objects/b319022a-4946-44c5-9de9-34801f95507b"></thr:in-reply-to>
|
||||
<link rel="related" href="https://testing.pleroma.lol/objects/b319022a-4946-44c5-9de9-34801f95507b"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4378601"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4378601" local_id="4378601" ref="tag:shitposter.club,2018-02-22:objectType=thread:nonce=e5a7c72d60a9c0e4">tag:shitposter.club,2018-02-22:objectType=thread:nonce=e5a7c72d60a9c0e4</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://testing.pleroma.lol/users/lain"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<source>
|
||||
<id>https://shitposter.club/api/statuses/user_timeline/5381.atom</id>
|
||||
<title>shp</title>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/shpuld"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/user_timeline/5381.atom"/>
|
||||
<link rel="license" href="https://shitposter.club/doc/tos"/>
|
||||
<icon>https://shitposter.club/avatar/5381-96-20171230093854.png</icon>
|
||||
<updated>2018-02-23T13:30:15+00:00</updated>
|
||||
</source>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7369654.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7369654.atom"/>
|
||||
<statusnet:notice_info local_id="7369654" source="Pleroma FE"></statusnet:notice_info>
|
||||
</entry>
|
665
test/fixtures/httpoison_mock/7369654.html
vendored
Normal file
665
test/fixtures/httpoison_mock/7369654.html
vendored
Normal file
|
@ -0,0 +1,665 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Shitposter Club</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0">
|
||||
<link rel="stylesheet" type="text/css" href="https://shitposter.club/plugins/Qvitter/css/qvitter.css?changed=20170610161937" />
|
||||
<link rel="stylesheet" type="text/css" href="https://shitposter.club/plugins/Qvitter/css/jquery.minicolors.css" />
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/apple-touch-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/apple-touch-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/apple-touch-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/apple-touch-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/apple-touch-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/apple-touch-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/apple-touch-icon-180x180.png">
|
||||
<link rel="icon" type="image/png" href="https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png" sizes="16x16">
|
||||
<link rel="icon" type="image/png" href="https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/android-chrome-192x192.png" sizes="192x192">
|
||||
<link rel="icon" type="image/png" href="https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-96x96.png" sizes="96x96">
|
||||
<link rel="manifest" href="https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/manifest.json">
|
||||
<link rel="mask-icon" href="https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/safari-pinned-tab.svg" color="#a22430">
|
||||
<meta name="apple-mobile-web-app-title" content="Shitposter Club">
|
||||
<meta name="application-name" content="Shitposter Club">
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="msapplication-TileImage" content="https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/mstile-144x144.png">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<link title="oEmbed" href="https://shitposter.club/services/oembed.json?url=https%3A%2F%2Fshitposter.club%2Fnotice%2F7369654" type="application/json+oembed" rel="alternate">
|
||||
<link title="oEmbed" href="https://shitposter.club/services/oembed.xml?url=https%3A%2F%2Fshitposter.club%2Fnotice%2F7369654" type="application/xml+oembed" rel="alternate">
|
||||
<link title="Single notice (JSON)" href="https://shitposter.club/api/statuses/show/7369654.json" type="application/stream+json" rel="alternate">
|
||||
<link title="Single notice (Atom)" href="https://shitposter.club/api/statuses/show/7369654.atom" type="application/atom+xml" rel="alternate">
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:title" content="shp (@shpuld)" />
|
||||
<meta name="twitter:description" content="@lain me far right" />
|
||||
<meta property="og:description" content="@lain me far right" />
|
||||
<meta property="og:site_name" content="Shitposter Club" />
|
||||
<script>
|
||||
/*
|
||||
@licstart The following is the entire license notice for the
|
||||
JavaScript code in this page.
|
||||
|
||||
Copyright (C) 2015 Hannes Mannerheim and other contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
@licend The above is the entire license notice
|
||||
for the JavaScript code in this page.
|
||||
*/
|
||||
|
||||
window.usersLanguageCode = "en";
|
||||
window.usersLanguageNameInEnglish = "English";
|
||||
window.englishLanguageData = {
|
||||
"directionality":"ltr",
|
||||
"languageName": "English",
|
||||
"loginUsername": "Username or e-mail",
|
||||
"loginPassword": "Password",
|
||||
"loginSignIn": "Sign in",
|
||||
"loginRememberMe": "Remember me",
|
||||
"loginForgotPassword": "Forgot password?",
|
||||
"notices": "Notices",
|
||||
"followers": "Followers",
|
||||
"following": "Following",
|
||||
"groups": "Groups",
|
||||
"compose": "PULL THE TRIGGER",
|
||||
"queetVerb": "Send",
|
||||
"queetsNounPlural": "Notices",
|
||||
"logout": "Sign out",
|
||||
"languageSelected": "Language:",
|
||||
"viewMyProfilePage": "View my profile page",
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse",
|
||||
"details": "Details",
|
||||
"expandFullConversation": "Expand full conversation",
|
||||
"replyVerb": "Reply",
|
||||
"requeetVerb": "Repeat",
|
||||
"favoriteVerb": "Favorite",
|
||||
"requeetedVerb": "Repeated",
|
||||
"favoritedVerb": "Favorited",
|
||||
"replyTo": "Reply to",
|
||||
"requeetedBy": "Repeated by {requeeted-by}",
|
||||
"favoriteNoun": "Favorite",
|
||||
"favoritesNoun": "Favorites",
|
||||
"requeetNoun": "Repeat",
|
||||
"requeetsNoun": "Repeats",
|
||||
"newQueet": "{new-notice-count} new notice",
|
||||
"newQueets": "{new-notice-count} new notices",
|
||||
"longmonthsJanuary": "January",
|
||||
"longmonthsFebruary": "February",
|
||||
"longmonthsMars": "March",
|
||||
"longmonthsApril": "April",
|
||||
"longmonthsMay": "May",
|
||||
"longmonthsJune": "June",
|
||||
"longmonthsJuly": "July",
|
||||
"longmonthsAugust": "August",
|
||||
"longmonthsSeptember": "September",
|
||||
"longmonthsOctober": "October",
|
||||
"longmonthsNovember": "November",
|
||||
"longmonthsDecember": "December",
|
||||
"shortmonthsJanuary": "jan",
|
||||
"shortmonthsFebruary": "feb",
|
||||
"shortmonthsMars": "mar",
|
||||
"shortmonthsApril": "apr",
|
||||
"shortmonthsMay": "may",
|
||||
"shortmonthsJune": "jun",
|
||||
"shortmonthsJuly": "jul",
|
||||
"shortmonthsAugust": "aug",
|
||||
"shortmonthsSeptember": "sep",
|
||||
"shortmonthsOctober": "oct",
|
||||
"shortmonthsNovember": "nov",
|
||||
"shortmonthsDecember": "dec",
|
||||
"time12am": "{time} am",
|
||||
"time12pm": "{time} pm",
|
||||
"longDateFormat": "{time12} - {day} {month} {year}",
|
||||
"shortDateFormatSeconds": "{seconds}s",
|
||||
"shortDateFormatMinutes": "{minutes}m",
|
||||
"shortDateFormatHours": "{hours}h",
|
||||
"shortDateFormatDate": "{day} {month}",
|
||||
"shortDateFormatDateAndY": "{day} {month} {year}",
|
||||
"now": "now",
|
||||
"posting": "posting",
|
||||
"viewMoreInConvBefore": "← View more in conversation",
|
||||
"viewMoreInConvAfter": "View more in conversation →",
|
||||
"mentions": "Mentions",
|
||||
"timeline": "Only Who I'm Following",
|
||||
"publicTimeline": "Everyone on Shitposter Club",
|
||||
"publicAndExtTimeline": "MY EYES! I CAN SEE FOREVER",
|
||||
"searchVerb": "Search",
|
||||
"deleteVerb": "Delete",
|
||||
"cancelVerb": "Cancel",
|
||||
"deleteConfirmation": "Are you sure you want to delete this notice?",
|
||||
"userExternalFollow": "Remote follow",
|
||||
"userExternalFollowHelp": "Your account ID (e.g. user@rainbowdash.net).",
|
||||
"userFollow": "Follow",
|
||||
"userFollowing": "Following",
|
||||
"userUnfollow": "Unfollow",
|
||||
"joinGroup": "Join",
|
||||
"joinExternalGroup": "Join remotely",
|
||||
"isMemberOfGroup": "Member",
|
||||
"leaveGroup": "Leave",
|
||||
"memberCount": "Members",
|
||||
"adminCount": "Admins",
|
||||
"settings": "Settings",
|
||||
"saveChanges": "Save changes",
|
||||
"linkColor": "Link color",
|
||||
"backgroundColor": "Background color",
|
||||
"newToQuitter": "New to {site-title}?",
|
||||
"signUp": "Sign up",
|
||||
"signUpFullName": "Full name",
|
||||
"signUpEmail": "Email",
|
||||
"signUpButtonText": "Sign up to {site-title}",
|
||||
"welcomeHeading": "Welcome to {site-title}.",
|
||||
"welcomeText": "We are a <span id=\"federated-tooltip\"><div id=\"what-is-federation\">\"Federation\" means that you don't need a {site-title} account to be able to follow, be followed by or interact with {site-title} users. You can register on any StatusNet or GNU social server or any service based on the the <a href=\"http://www.w3.org/community/ostatus/wiki/Main_Page\">Ostatus</a> protocol! You don't even have to join a service – try installing the lovely <a href=\"http://www.gnu.org/software/social/\">GNU social</a> software on your own server! :)</div>federation</span> of microbloggers who care about social justice and solidarity and want to quit the centralised capitalist services.",
|
||||
"registerNickname": "Nickname",
|
||||
"registerHomepage": "Homepage",
|
||||
"registerBio": "Bio",
|
||||
"registerLocation": "Location",
|
||||
"registerRepeatPassword": "Repeat password",
|
||||
"moreSettings": "More settings",
|
||||
"otherServers": "Alternatively you can create an account on another server of the GNU social network. <a href=\"http://federation.skilledtests.com/select_your_server.html\">Comparison</a>",
|
||||
"editMyProfile": "Edit profile",
|
||||
"notifications": "Notifications",
|
||||
"xFavedYourQueet": "favorited your notice",
|
||||
"xRepeatedYourQueet": "repeated you",
|
||||
"xStartedFollowingYou": "followed you",
|
||||
"followsYou": "follows you",
|
||||
"FAQ": "FAQ",
|
||||
"inviteAFriend": "Invite a friend!",
|
||||
"goToExternalProfile": "Go to full profile",
|
||||
"cropAndSave": "Crop and save",
|
||||
"showTerms": "Read our Terms of Use",
|
||||
"ellipsisMore": "More",
|
||||
"blockUser": "Block",
|
||||
"goToOriginalNotice": "Go to the original notice",
|
||||
"goToTheUsersRemoteProfile": "Go to the user's remote profile",
|
||||
"clickToDrag":"Click to drag",
|
||||
"keyboardShortcuts":"Keyboard shortcuts",
|
||||
"classicInterface":"Classic {site-title}",
|
||||
"accessibilityToggleLink":"For better accessibility, click this link to switch to the classic interface",
|
||||
"tooltipBookmarkStream":"Add this stream to your bookmarks",
|
||||
"tooltipTopMenu":"Menu and settings",
|
||||
"tooltipAttachImage":"Attach an image",
|
||||
"tooltipShortenUrls":"Shorten all URLs in the notice",
|
||||
"tooltipReloadStream":"Refresh this stream",
|
||||
"tooltipRemoveBookmark":"Remove this bookmark",
|
||||
"clearHistory":"Clear browsing history",
|
||||
"ERRORsomethingWentWrong":"Something went wrong.",
|
||||
"ERRORmustBeLoggedIn":"You must be logged in to view this stream.",
|
||||
"ERRORcouldNotFindUserWithNickname":"Could not find a user with nickname \"{nickname}\" on this server",
|
||||
"ERRORcouldNotFindGroupWithNickname":"Could not find a group with nickname \"{nickname}\" on this server",
|
||||
"ERRORcouldNotFindPage":"Could not find that page.",
|
||||
"ERRORnoticeRemoved": "This notice has been removed.",
|
||||
"ERRORnoContactWithServer": "Can not establish a connection to the server. The server could be overloaded, or there might be a problem with your internet connection. Please try again later!",
|
||||
"ERRORattachmentUploadFailed": "The upload failed. The format might be unsupported or the size too large.",
|
||||
"hideRepliesToPeopleIDoNotFollow":"Hide replies to people I don't follow",
|
||||
"markAllNotificationsAsSeen":"Mark all notifications as seen",
|
||||
"notifyRepliesAndMentions":"Mentions and replies",
|
||||
"notifyFavs":"Favorites",
|
||||
"notifyRepeats":"Repeats",
|
||||
"notifyFollows":"New followers",
|
||||
"timelineOptions":"Timeline options",
|
||||
"ERRORfailedSavingYourSetting":"Failed saving your setting",
|
||||
"ERRORfailedMarkingAllNotificationsAsRead":"Failed marking all notifications as seen.",
|
||||
"newNotification": "{new-notice-count} new notification",
|
||||
"newNotifications": "{new-notice-count} new notifications",
|
||||
"thisIsANoticeFromABlockedUser":"Warning: This is a quip from a user you have blocked. Click to show it.",
|
||||
"nicknamesListWithListName":"{nickname}’s list: {list-name}",
|
||||
"myListWithListName":"My list: {list-name}",
|
||||
"listMembers":"Members",
|
||||
"listSubscribers":"Subscribers",
|
||||
"ERRORcouldNotFindList":"There is no such list.",
|
||||
"emailAlreadyInUse":"Already in use",
|
||||
"addEditLanguageLink":"Help translate {site-title} to another language",
|
||||
"onlyPartlyTranslated":"{site-title} is only partly translated to <em>{language-name}</em> ({percent}%). You can help complete the translation at <a href=\"https://git.gnu.io/h2p/Qvitter/tree/master/locale\">Qvitter's repository homepage</a>",
|
||||
"startRant":"Start a rant",
|
||||
"continueRant":"Continue the rant",
|
||||
"hideEmbeddedInTimeline":"Hide embedded content in this timeline",
|
||||
"hideQuotesInTimeline":"Hide quotes in this timeline",
|
||||
"userBlocks":"Accounts you're blocking",
|
||||
"buttonBlocked":"Blocked",
|
||||
"buttonUnblock":"Unblock",
|
||||
"failedBlockingUser":"Failed to block the user.",
|
||||
"failedUnblockingUser":"Failed to unblock the user.",
|
||||
"unblockUser": "Unblock",
|
||||
"tooltipBlocksYou":"You are blocked from following {username}.",
|
||||
"silenced":"Silenced",
|
||||
"silencedPlural":"Silenced profiles",
|
||||
"silencedUsersOnThisInstance":"Silenced profiles on {site-title}",
|
||||
"sandboxed":"Sandboxed",
|
||||
"sandboxedPlural":"Sandboxed profiles",
|
||||
"sandboxedUsersOnThisInstance":"Sandboxed profiles on {site-title}",
|
||||
"silencedStreamDescription":"Silenced users can't login or post quips and the quips they've already posted are hidden. For local users it's like a delete that can be reversed, for remote users it's like a site wide block.",
|
||||
"sandboxedStreamDescription":"Quips from sandboxed users are excluded from the Public Timeline and The Whole Known Network. Apart from that, they can use the site like any other user.",
|
||||
"onlyShowNotificationsFromUsersIFollow":"Only show notifications from users I follow",
|
||||
"userOptions":"More user actions",
|
||||
"silenceThisUser":"Silence {nickname}",
|
||||
"sandboxThisUser":"Sandbox {nickname}",
|
||||
"unSilenceThisUser":"Unsilence {nickname}",
|
||||
"unSandboxThisUser":"Unsandbox {nickname}",
|
||||
"ERRORfailedSandboxingUser":"Failed sandboxing/unsandboxing the user",
|
||||
"ERRORfailedSilencingUser":"Failed silencing/unsilencing the user",
|
||||
"muteUser":"Mute",
|
||||
"unmuteUser":"Unmute",
|
||||
"hideNotificationsFromMutedUsers":"Hide notifications from muted users",
|
||||
"thisIsANoticeFromAMutedUser":"You have muted the author of this quip. Click here to show it anyway.",
|
||||
"userMutes":"Accounts you're muting",
|
||||
"userBlocked":"Blocked accounts",
|
||||
"userMuted":"Muted accounts",
|
||||
"mutedStreamDescription":"You've hidden these accounts from your timeline. You will still receive notifications from these accounts, unless you select "Hide notifications from muted users" from the cog wheel menu on the notifications page.",
|
||||
"profileAndSettings":"Profile and settings",
|
||||
"profileSettings":"Profile settings",
|
||||
"thisIsABookmark":"This is a bookmark created in the Classic interface",
|
||||
"thisIsARemoteUser":"<strong>Attention!</strong> This is a remote user. This page is only a cached copy of their profile, and includes only data known to this GNU social instance. Go to the <a href=\"{remote-profile-url}\" donthijack>user's profile on their server</a> to view their full profile.",
|
||||
"findSomeone":"Find someone",
|
||||
"findSomeoneTooltip":"Input a username or a profile url, e.g. @localuser or https://remote.instance/nickname",
|
||||
"tooltipAttachFile":"Attach a file"
|
||||
}
|
||||
;
|
||||
window.defaultAvatarStreamSize = "https:\/\/shitposter.club\/theme\/neo-gnu\/default-avatar-stream.png";
|
||||
window.defaultAvatarProfileSize = "https:\/\/shitposter.club\/theme\/neo-gnu\/default-avatar-profile.png";
|
||||
window.textLimit = 3800;
|
||||
window.registrationsClosed = false;
|
||||
window.thisSiteThinksItIsHttpButIsActuallyHttps = false;
|
||||
window.siteTitle = "Shitposter Club";
|
||||
window.loggedIn = false;
|
||||
window.timeBetweenPolling = 5000;
|
||||
window.apiRoot = 'https://shitposter.club/api/';
|
||||
window.fullUrlToThisQvitterApp = 'https://shitposter.club/plugins/Qvitter/';
|
||||
window.siteRootDomain = 'shitposter.club';
|
||||
window.siteInstanceURL = 'https://shitposter.club/';
|
||||
window.avatarServer= "";
|
||||
window.defaultLinkColor = '#0084B4';
|
||||
window.defaultBackgroundColor = '#f4f4f4';
|
||||
window.siteBackground = '../../file/cityscape.jpg';
|
||||
window.enableWelcomeText = true;
|
||||
window.customWelcomeText = {"en":"<h1 style=\"text-align: center;\"><img src=\"\/custom\/spclublogo-05.png\" alt=\"Shitposter Club\"><br>A safe space on the Internet<\/h1>"};
|
||||
window.urlShortenerAPIURL = 'http://qttr.at/yourls-api.php';
|
||||
window.urlShortenerSignature = 'b6afeec983';
|
||||
window.urlshortenerFormat = 'jsonp';
|
||||
window.commonSessionToken = '99dbb9040190c2c0d1e0a991204b088116ba434cfcf532c2d423fdbd67647d1a2b737b446dfd81579980e6acd53ad37974801547b69f293e008f45bd5b89bc4a';
|
||||
window.siteMaxThumbnailSize = 1000;
|
||||
window.siteAttachmentURLBase = 'https://shitposter.club//file/';
|
||||
window.siteAvatarURLBase = 'https://shitposter.club//avatar/';
|
||||
window.siteEmail = 'shitposterclub@gmail.com';
|
||||
window.siteLicenseTitle = '';
|
||||
window.siteLicenseURL = 'https://shitposter.club/doc/tos';
|
||||
window.customTermsOfUse = "<h2>The Rules<\/h2>\n<ol>\n<li>Do not post content that is illegal in the United States of America.<\/li>\n<li>Do not engage in behavior onsite that would get the admin or his hosting\nthreatened, e.g. doxing, harassment, posting copyrighted content that\nwill get the site DMCA'd, etc. This is a vague rule, sorry, it can't be\nhelped.<\/li>\n<li>The site should be considered NOT SAFE FOR WORK (NSFW), <em>however<\/em>,\nwe DO NOT allow: \n <ul>\n <li>\"excessive or extreme pornography\"<\/li>\n <li>gore or \"gross-out\" (e.g. \"tubgirl\") pics<\/li>\n <li>so-called \"loli hentai\" aka sexually explicit drawn depictions of children<\/li>\n <li>\"child model\" pictures<\/li>\n <\/ul>\n ...on the \"public\" (\"everyone on Shitposter Club\") timeline.\n <p>\n What this means is, do not post these pictures, or \"repeat\" them from The Whole Known Network (\"My eyes!\") timeline, or embed them.<\/li>\n<li>Do not engage in behavior that harms the functionality of the site\nitself, e.g. no hacking or exploiting it or spamming. If you're told you're doing\nsomething that is harming the technical operation of the site, stop doing it. The\nadmin's word is final.<\/li>\n<\/ol>\n<h2>My Pledge to You<\/h2>\n<p>I will not ban you or delete your posts for:\nBeing a jerk, having a terrible opinion, disagreeing with me, engaging in so-called \"hate\" or \"offensive\" speech (we have a block button, use it.)<\/p>\n<p>I will ban you or delete your posts for:\nBreaking the rules above, intentionally evading a block to post directly\nat someone who has blocked you, basically antisocial behavior that\ndirectly tries to get around any of the other rules. I will TRY to be lenient and tolerant about rules and not be a ban-Nazi.<\/p>\n<p>You own your posts, but due to the nature of federated services you\nare granting an irrevocable license for others on the network to\nsyndicate it. You are responsible for what you post.<\/p>";
|
||||
window.siteLocalOnlyDefaultPath = true;
|
||||
window.disableKeyboardShortcuts = false;
|
||||
// available language files and their last update time
|
||||
window.availableLanguages = {
|
||||
"ar": "ar.json?changed=20170610161937",
|
||||
"ast": "ast.json?changed=20170610161937",
|
||||
"ca": "ca.json?changed=20170610161937",
|
||||
"de": "de.json?changed=20170610161937",
|
||||
"en": "en.json?changed=20170610161937",
|
||||
"eo": "eo.json?changed=20170610161937",
|
||||
"es_419": "es_419.json?changed=20170610161937",
|
||||
"es": "es.json?changed=20170610161937",
|
||||
"eu": "eu.json?changed=20170610161937",
|
||||
"fa": "fa.json?changed=20170610161937",
|
||||
"fi": "fi.json?changed=20170610161937",
|
||||
"fr": "fr.json?changed=20170610161937",
|
||||
"gl": "gl.json?changed=20170610161937",
|
||||
"he": "he.json?changed=20170610161937",
|
||||
"hy": "hy.json?changed=20170610161937",
|
||||
"ia": "ia.json?changed=20170610161937",
|
||||
"io": "io.json?changed=20170610161937",
|
||||
"it": "it.json?changed=20170610161937",
|
||||
"ja": "ja.json?changed=20170610161937",
|
||||
"nb": "nb.json?changed=20170610161937",
|
||||
"nl": "nl.json?changed=20170610161937",
|
||||
"pl": "pl.json?changed=20170610161937",
|
||||
"pt_br": "pt_br.json?changed=20170610161937",
|
||||
"pt": "pt.json?changed=20170610161937",
|
||||
"ru": "ru.json?changed=20170610161937",
|
||||
"sq": "sq.json?changed=20170610161937",
|
||||
"sv": "sv.json?changed=20170610161937",
|
||||
"tr": "tr.json?changed=20170610161937",
|
||||
"uk": "uk.json?changed=20170610161937",
|
||||
"zh_cn": "zh_cn.json?changed=20170610161937",
|
||||
"zh_tw": "zh_tw.json?changed=20170610161937",
|
||||
};
|
||||
|
||||
</script>
|
||||
<link href='https://shitposter.club/plugins/QvitterSimpleSecurity/css/ss.css?changed=20160925025913' rel='stylesheet' type='text/css'> </head>
|
||||
<body class="" style="background-color:#f4f4f4">
|
||||
<input id="upload-image-input" class="upload-image-input" type="file" name="upload-image-input">
|
||||
<div class="topbar">
|
||||
<a href="https://shitposter.club/main/public"><div id="logo"></div></a><div id="top-compose" class="hidden"></div>
|
||||
<ul class="quitter-settings dropdown-menu">
|
||||
<li class="dropdown-caret right">
|
||||
<span class="caret-outer"></span>
|
||||
<span class="caret-inner"></span>
|
||||
</li>
|
||||
<li class="fullwidth"><a id="top-menu-profile-link" class="no-hover-card" href="https://shitposter.club/"><div id="top-menu-profile-link-fullname"></div><div id="top-menu-profile-link-view-profile"></div></a></li>
|
||||
<li class="fullwidth dropdown-divider"></li>
|
||||
<li class="fullwidth"><a id="faq-link"></a></li>
|
||||
<li class="fullwidth"><a id="tou-link"></a></li>
|
||||
<li class="fullwidth"><a id="shortcuts-link"></a></li> <li class="fullwidth"><a id="invite-link" href="https://shitposter.club/main/invite"></a></li>
|
||||
<li class="fullwidth"><a id="classic-link"></a></li>
|
||||
<li class="fullwidth dropdown-divider"></li>
|
||||
<li class="fullwidth"><a id="logout"></a></li>
|
||||
<li class="fullwidth language dropdown-divider"></li>
|
||||
<li class="language"><a class="language-link" data-tooltip="العربية – Arabic" data-lang-code="ar">العربية</a></li><li class="language"><a class="language-link" data-tooltip="asturianu – Asturian" data-lang-code="ast">asturianu</a></li><li class="language"><a class="language-link" data-tooltip="català – Catalan" data-lang-code="ca">català</a></li><li class="language"><a class="language-link" data-tooltip="Deutsch – German" data-lang-code="de">Deutsch</a></li><li class="language"><a class="language-link" data-tooltip="English" data-lang-code="en">English</a></li><li class="language"><a class="language-link" data-tooltip="esperanto – Esperanto" data-lang-code="eo">esperanto</a></li><li class="language"><a class="language-link" data-tooltip="español (Latinoamérica) – Spanish (Latin America)" data-lang-code="es_419">español (Latinoamérica)</a></li><li class="language"><a class="language-link" data-tooltip="español – Spanish" data-lang-code="es">español</a></li><li class="language"><a class="language-link" data-tooltip="euskara – Basque" data-lang-code="eu">euskara</a></li><li class="language"><a class="language-link" data-tooltip="فارسی – Persian" data-lang-code="fa">فارسی</a></li><li class="language"><a class="language-link" data-tooltip="suomi – Finnish" data-lang-code="fi">suomi</a></li><li class="language"><a class="language-link" data-tooltip="français – French" data-lang-code="fr">français</a></li><li class="language"><a class="language-link" data-tooltip="galego – Galician" data-lang-code="gl">galego</a></li><li class="language"><a class="language-link" data-tooltip="עברית – Hebrew" data-lang-code="he">עברית</a></li><li class="language"><a class="language-link" data-tooltip="հայերեն – Armenian" data-lang-code="hy">հայերեն</a></li><li class="language"><a class="language-link" data-tooltip="Interlingua" data-lang-code="ia">Interlingua</a></li><li class="language"><a class="language-link" data-tooltip="Ido" data-lang-code="io">Ido</a></li><li class="language"><a class="language-link" data-tooltip="italiano – Italian" data-lang-code="it">italiano</a></li><li class="language"><a class="language-link" data-tooltip="日本語 – Japanese" data-lang-code="ja">日本語</a></li><li class="language"><a class="language-link" data-tooltip="norsk bokmål – Norwegian Bokmål" data-lang-code="nb">norsk bokmål</a></li><li class="language"><a class="language-link" data-tooltip="Nederlands – Dutch" data-lang-code="nl">Nederlands</a></li><li class="language"><a class="language-link" data-tooltip="polski – Polish" data-lang-code="pl">polski</a></li><li class="language"><a class="language-link" data-tooltip="português (Brasil) – Portuguese (Brazil)" data-lang-code="pt_br">português (Brasil)</a></li><li class="language"><a class="language-link" data-tooltip="português – Portuguese" data-lang-code="pt">português</a></li><li class="language"><a class="language-link" data-tooltip="русский – Russian" data-lang-code="ru">русский</a></li><li class="language"><a class="language-link" data-tooltip="shqip – Albanian" data-lang-code="sq">shqip</a></li><li class="language"><a class="language-link" data-tooltip="svenska – Swedish" data-lang-code="sv">svenska</a></li><li class="language"><a class="language-link" data-tooltip="Türkçe – Turkish" data-lang-code="tr">Türkçe</a></li><li class="language"><a class="language-link" data-tooltip="українська – Ukrainian" data-lang-code="uk">українська</a></li><li class="language"><a class="language-link" data-tooltip="中文(中国) – Chinese (China)" data-lang-code="zh_cn">中文(中国)</a></li><li class="language"><a class="language-link" data-tooltip="中文(台灣) – Chinese (Taiwan)" data-lang-code="zh_tw">中文(台灣)</a></li> <li class="fullwidth language dropdown-divider"></li>
|
||||
<li class="fullwidth"><a href="https://git.gnu.io/h2p/Qvitter/tree/master/locale" target="_blank" id="add-edit-language-link"></a></li>
|
||||
</ul>
|
||||
<div class="global-nav">
|
||||
<div class="global-nav-inner">
|
||||
<div class="container">
|
||||
<div id="search">
|
||||
<input type="text" spellcheck="false" autocomplete="off" name="q" placeholder="Sök" id="search-query" class="search-input">
|
||||
<span class="search-icon">
|
||||
<button class="icon nav-search" type="submit" tabindex="-1">
|
||||
<span> Sök </span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<ul class="language-dropdown">
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle">
|
||||
<small></small>
|
||||
<span class="current-language"></span>
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="dropdown-caret right">
|
||||
<span class="caret-outer"></span>
|
||||
<span class="caret-inner"></span>
|
||||
</li>
|
||||
<li><a class="language-link" data-tooltip="Arabic" data-lang-code="ar">العربية</a></li><li><a class="language-link" data-tooltip="Asturian" data-lang-code="ast">asturianu</a></li><li><a class="language-link" data-tooltip="Catalan" data-lang-code="ca">català</a></li><li><a class="language-link" data-tooltip="German" data-lang-code="de">Deutsch</a></li><li><a class="language-link" data-tooltip="English" data-lang-code="en">English</a></li><li><a class="language-link" data-tooltip="Esperanto" data-lang-code="eo">esperanto</a></li><li><a class="language-link" data-tooltip="Spanish (Latin America)" data-lang-code="es_419">español (Latinoamérica)</a></li><li><a class="language-link" data-tooltip="Spanish" data-lang-code="es">español</a></li><li><a class="language-link" data-tooltip="Basque" data-lang-code="eu">euskara</a></li><li><a class="language-link" data-tooltip="Persian" data-lang-code="fa">فارسی</a></li><li><a class="language-link" data-tooltip="Finnish" data-lang-code="fi">suomi</a></li><li><a class="language-link" data-tooltip="French" data-lang-code="fr">français</a></li><li><a class="language-link" data-tooltip="Galician" data-lang-code="gl">galego</a></li><li><a class="language-link" data-tooltip="Hebrew" data-lang-code="he">עברית</a></li><li><a class="language-link" data-tooltip="Armenian" data-lang-code="hy">հայերեն</a></li><li><a class="language-link" data-tooltip="Interlingua" data-lang-code="ia">Interlingua</a></li><li><a class="language-link" data-tooltip="Ido" data-lang-code="io">Ido</a></li><li><a class="language-link" data-tooltip="Italian" data-lang-code="it">italiano</a></li><li><a class="language-link" data-tooltip="Japanese" data-lang-code="ja">日本語</a></li><li><a class="language-link" data-tooltip="Norwegian Bokmål" data-lang-code="nb">norsk bokmål</a></li><li><a class="language-link" data-tooltip="Dutch" data-lang-code="nl">Nederlands</a></li><li><a class="language-link" data-tooltip="Polish" data-lang-code="pl">polski</a></li><li><a class="language-link" data-tooltip="Portuguese (Brazil)" data-lang-code="pt_br">português (Brasil)</a></li><li><a class="language-link" data-tooltip="Portuguese" data-lang-code="pt">português</a></li><li><a class="language-link" data-tooltip="Russian" data-lang-code="ru">русский</a></li><li><a class="language-link" data-tooltip="Albanian" data-lang-code="sq">shqip</a></li><li><a class="language-link" data-tooltip="Swedish" data-lang-code="sv">svenska</a></li><li><a class="language-link" data-tooltip="Turkish" data-lang-code="tr">Türkçe</a></li><li><a class="language-link" data-tooltip="Ukrainian" data-lang-code="uk">українська</a></li><li><a class="language-link" data-tooltip="Chinese (China)" data-lang-code="zh_cn">中文(中国)</a></li><li><a class="language-link" data-tooltip="Chinese (Taiwan)" data-lang-code="zh_tw">中文(台灣)</a></li> </ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="no-js-error">Please enable javascript to use this site.<script>var element = document.getElementById('no-js-error'); element.parentNode.removeChild(element);</script></div>
|
||||
<div id="page-container">
|
||||
<div id="site-notice"><h1 style="color: white">WARNING: this site filled with KREMLIN TROLLS</h1><div id="site-notice-minimize">_</div></div> <div class="front-welcome-text "></div>
|
||||
<div id="login-register-container">
|
||||
<div id="login-content">
|
||||
<form id="form_login" class="form_settings" action="https://shitposter.club/main/qlogin" method="post">
|
||||
<div id="username-container">
|
||||
<input id="nickname" name="nickname" type="text" value="" tabindex="1" />
|
||||
</div>
|
||||
<table class="password-signin"><tbody><tr>
|
||||
<td class="flex-table-primary">
|
||||
<div class="placeholding-input">
|
||||
<input id="password" name="password" type="password" tabindex="2" value="" />
|
||||
</div>
|
||||
</td>
|
||||
<td class="flex-table-secondary">
|
||||
<button class="submit" type="submit" id="submit-login" tabindex="4"></button>
|
||||
</td>
|
||||
</tr></tbody></table>
|
||||
<div id="remember-forgot">
|
||||
<input type="checkbox" id="rememberme" name="rememberme" value="yes" tabindex="3" checked="checked"> <span id="rememberme_label"></span> · <a id="forgot-password" href="https://shitposter.club/main/recoverpassword" ></a>
|
||||
<input type="hidden" id="token" name="token" value="99dbb9040190c2c0d1e0a991204b088116ba434cfcf532c2d423fdbd67647d1a2b737b446dfd81579980e6acd53ad37974801547b69f293e008f45bd5b89bc4a">
|
||||
<a href="https://shitposter.club/main/openid" id="openid-login" title="OpenID" donthijack>OpenID</a> </div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="front-signup">
|
||||
<h2></h2>
|
||||
<div class="signup-input-container"><input placeholder="" type="text" name="user[name]" autocomplete="off" class="text-input" id="signup-user-name"></div>
|
||||
<div class="signup-input-container"><input placeholder="" type="text" name="user[email]" autocomplete="off" id="signup-user-email"></div>
|
||||
<div class="signup-input-container"><input placeholder="" type="password" name="user[user_password]" class="text-input" id="signup-user-password"></div>
|
||||
<button id="signup-btn-step1" class="signup-btn" type="submit"></button>
|
||||
</div>
|
||||
<div id="other-servers-link"></div><div id="qvitter-notice-logged-out"></div></div>
|
||||
<div id="feed">
|
||||
<div id="feed-header">
|
||||
<div id="feed-header-inner">
|
||||
<h2>
|
||||
<span id="stream-header"></span>
|
||||
</h2>
|
||||
<div class="reload-stream"></div>
|
||||
</div>
|
||||
<div id="feed-header-description"></div>
|
||||
</div>
|
||||
<div id="new-queets-bar-container" class="hidden"><div id="new-queets-bar"></div></div>
|
||||
<div id="feed-body"></div>
|
||||
</div>
|
||||
<div id="hidden-html"><ol class="notices xoxo"><style type="text/css" media="">.greentext { color: green; }</style>
|
||||
<style type="text/css" media="">
|
||||
.sensitive-blocker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.stream-item.notice.sensitive-notice .sensitive-blocker {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
/*background-color: #d4baba;*/
|
||||
background-color: black;
|
||||
background-image: url(/custom/afterdark.jpg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: contain;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
|
||||
.sensitive-blocker:hover {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
div.stream-item.notice.expanded.sensitive-notice .sensitive-blocker {
|
||||
display: none;
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css" media="">span.dicerolls { font-weight: bold; border: 1px solid black; }</style>
|
||||
<li class="h-entry notice post notice-source-PleromaFE" id="notice-7369654">
|
||||
<span class="tagcontainer">
|
||||
<section class="notice-headers">
|
||||
<a href="https://shitposter.club/notice/7369654" class="notice-title">shp (shpuld)'s status on Thursday, 22-Feb-2018 09:20:12 UTC</a>
|
||||
<a href="https://shitposter.club/shpuld" class="h-card p-author" title="shpuld">
|
||||
<img src="https://shitposter.club/avatar/5381-48-20171230093854.png" class="avatar u-photo" width="48" height="48" alt="shp"/>
|
||||
shp</a>
|
||||
<div class="parents">
|
||||
<a href="https://testing.pleroma.lol/objects/b319022a-4946-44c5-9de9-34801f95507b" class="u-in-reply-to" rel="in-reply-to">in reply to</a>
|
||||
<ul class="addressees">
|
||||
<li class="h-card">
|
||||
<a href="https://testing.pleroma.lol/users/lain" title="lain" class="addressee account">Rael Electric Razor</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<article class="e-content">@<a href="https://testing.pleroma.lol/users/lain" class="h-card mention" title="Rael Electric Razor">lain</a> me far right</article>
|
||||
<footer>
|
||||
<a rel="bookmark" class="timestamp" href="https://shitposter.club/conversation/4378601#notice-7369654">
|
||||
<time class="dt-published" datetime="2018-02-22T09:20:12+00:00" title="Thursday, 22-Feb-2018 09:20:12 UTC">about a day ago</time>
|
||||
</a>
|
||||
<span class="source">from <span class="device">Pleroma FE</span>
|
||||
</span>
|
||||
<a href="https://shitposter.club/notice/7369654" class="permalink u-url">permalink</a>
|
||||
</footer>
|
||||
</span>
|
||||
</li>
|
||||
</ol></div>
|
||||
<div id="footer"><div id="footer-spinner-container"></div></div>
|
||||
</div>
|
||||
<script type="text/javascript" src="https://shitposter.club/plugins/Qvitter/js/lib/jquery-2.1.4.min.js?changed=20170610161937"></script>
|
||||
<script type="text/javascript" src="https://shitposter.club/plugins/Qvitter/js/lib/jquery-ui.min.js?changed=20170610161937"></script>
|
||||
<script type="text/javascript" src="https://shitposter.club/plugins/Qvitter/js/lib/jquery.minicolors.min.js?changed=20170610161937"></script>
|
||||
<script type="text/javascript" src="https://shitposter.club/plugins/Qvitter/js/lib/jquery.jWindowCrop.js?changed=20170610161937"></script>
|
||||
<script type="text/javascript" src="https://shitposter.club/plugins/Qvitter/js/lib/load-image.min.js?changed=20170610161937"></script>
|
||||
<script type="text/javascript" src="https://shitposter.club/plugins/Qvitter/js/lib/xregexp-all-3.0.0-pre.js?changed=20170610161937"></script>
|
||||
<script type="text/javascript" src="https://shitposter.club/plugins/Qvitter/js/lib/lz-string.js?changed=20170610161937"></script>
|
||||
<script type="text/javascript" src="https://shitposter.club/plugins/Qvitter/js/lib/bowser.min.js?changed=20170610161937"></script>
|
||||
<script charset="utf-8" type="text/javascript" src="https://shitposter.club/plugins/Qvitter/js/dom-functions.js?changed=20170830220115"></script>
|
||||
<script charset="utf-8" type="text/javascript" src="https://shitposter.club/plugins/Qvitter/js/misc-functions.js?changed=20170610161937"></script>
|
||||
<script charset="utf-8" type="text/javascript" src="https://shitposter.club/plugins/Qvitter/js/ajax-functions.js?changed=20170610161937"></script>
|
||||
<script charset="utf-8" type="text/javascript" src="https://shitposter.club/plugins/Qvitter/js/stream-router.js?changed=20170610161937"></script>
|
||||
<script charset="utf-8" type="text/javascript" src="https://shitposter.club/plugins/Qvitter/js/qvitter.js?changed=20170610161937"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/custom/spc.css">
|
||||
<script src="/custom/spc.js"></script>
|
||||
<meta property="og:image" content="http://shitposter.club/custom/ogimage.jpg" />
|
||||
<meta property="og:title" content="Shitposter Club, a safe space on the Internet" />
|
||||
<meta property="og:description" content="╔═════════════════ ೋღ☃ღೋ ════════════════╗
|
||||
~ ~ ~ ~ ~ ~ ~ ~ ~ Reshare this if~ ~ ~ ~ ~ ~ ~ ~ ~
|
||||
~ ~ ~ ~ We are a beautiful strong Social Media ~ ~ ~
|
||||
~ ~ ~ ~ ~ ~ ~ who don’t need no man ~ ~ ~ ~ ~ ~ ~
|
||||
╚═════════════════ ೋღ☃ღೋ ════════════════╝" /><script src="https://shitposter.club/plugins/SPCEnhancements//js/audio-metadata.min.js"></script><script src='https://shitposter.club/plugins/QvitterSimpleSecurity/js/ss.js?changed=20160925025913'></script><style>
|
||||
img.emoji {
|
||||
width: auto;
|
||||
height: 1.5em;
|
||||
display: inline-block;
|
||||
margin-bottom: -0.25em;
|
||||
}
|
||||
.queet-text {
|
||||
padding-bottom: .25em;
|
||||
}
|
||||
</style>
|
||||
<script src="https://shitposter.club/plugins/Emojify/js/emojify.js"></script>
|
||||
<script>
|
||||
emojify.setConfig({
|
||||
img_dir: "https://shitposter.club/plugins/Emojify/images/emoji",
|
||||
ignore_emoticons: true
|
||||
});
|
||||
|
||||
var emojiReplacer = function(emoji, name, isEmoticon){
|
||||
var classes = (isEmoticon ? "emoticon" : "emoji") + " emoji-" + name;
|
||||
return '<span class="'+classes+'">'+emoji+'</span>';
|
||||
}
|
||||
</script>
|
||||
<script src="https://shitposter.club/plugins/Emojify/js/qvitter_event.js"></script> <div id="dynamic-styles">
|
||||
<style>
|
||||
a, a:visited, a:active,
|
||||
ul.stats li:hover a,
|
||||
ul.stats li:hover a strong,
|
||||
#user-body a:hover div strong,
|
||||
#user-body a:hover div div,
|
||||
.permalink-link:hover,
|
||||
.stream-item.expanded > .queet .stream-item-expand,
|
||||
.stream-item-footer .with-icn .requeet-text a b:hover,
|
||||
.queet-text span.attachment.more,
|
||||
.stream-item-header .created-at a:hover,
|
||||
.stream-item-header a.account-group:hover .name,
|
||||
.queet:hover .stream-item-expand,
|
||||
.show-full-conversation:hover,
|
||||
#new-queets-bar,
|
||||
.menu-container div,
|
||||
.cm-mention, .cm-tag, .cm-group, .cm-url, .cm-email,
|
||||
div.syntax-middle span,
|
||||
#user-body strong,
|
||||
ul.stats,
|
||||
.stream-item:not(.temp-post) ul.queet-actions li .icon:not(.is-mine):hover:before,
|
||||
.show-full-conversation,
|
||||
#user-body #user-queets:hover .label,
|
||||
#user-body #user-groups:hover .label,
|
||||
#user-body #user-following:hover .label,
|
||||
ul.stats a strong,
|
||||
.queet-box-extras button,
|
||||
#openid-login:hover:after,
|
||||
.post-to-group,
|
||||
.stream-item-header .addressees .reply-to .h-card.not-mentioned-inline {
|
||||
color:/*COLORSTART*/#0084B4/*COLOREND*/;
|
||||
}
|
||||
/*#unseen-notifications,*/
|
||||
.stream-item.notification.not-seen > .queet::before,
|
||||
#top-compose,
|
||||
#logo,
|
||||
.queet-toolbar button,
|
||||
#user-header,
|
||||
.profile-header-inner,
|
||||
.topbar,
|
||||
.menu-container,
|
||||
.member-button.member,
|
||||
.external-follow-button.following,
|
||||
.qvitter-follow-button.following,
|
||||
.save-profile-button,
|
||||
.crop-and-save-button,
|
||||
.topbar .global-nav.show-logo:before,
|
||||
.topbar .global-nav.pulse-logo:before,
|
||||
.dropdown-menu li:not(.dropdown-caret) a:hover {
|
||||
background-color:/*BACKGROUNDCOLORSTART*/#0084B4/*BACKGROUNDCOLOREND*/;
|
||||
}
|
||||
.queet-box-syntax[contenteditable="true"]:focus,
|
||||
.stream-item.selected-by-keyboard::before {
|
||||
border-color:/*BORDERCOLORSTART*/#999999/*BORDERCOLOREND*/;
|
||||
}
|
||||
#user-footer-inner,
|
||||
.inline-reply-queetbox,
|
||||
#popup-faq #faq-container p.indent,
|
||||
#find-someone {
|
||||
background-color:/*LIGHTERBACKGROUNDCOLORSTART*/rgb(205,230,239)/*LIGHTERBACKGROUNDCOLOREND*/;
|
||||
}
|
||||
#user-footer-inner,
|
||||
.queet-box,
|
||||
.queet-box-syntax[contenteditable="true"],
|
||||
.inline-reply-queetbox,
|
||||
span.inline-reply-caret,
|
||||
.stream-item.expanded .stream-item.first-visible-after-parent,
|
||||
#popup-faq #faq-container p.indent,
|
||||
.post-to-group,
|
||||
.quoted-notice:hover,
|
||||
.oembed-item:hover,
|
||||
.stream-item:hover:not(.expanded) .quoted-notice:hover,
|
||||
.stream-item:hover:not(.expanded) .oembed-item:hover,
|
||||
#find-someone input:focus {
|
||||
border-color:/*LIGHTERBORDERCOLORSTART*/rgb(155,206,224)/*LIGHTERBORDERCOLOREND*/;
|
||||
}
|
||||
span.inline-reply-caret .caret-inner {
|
||||
border-bottom-color:/*LIGHTERBORDERBOTTOMCOLORSTART*/rgb(205,230,239)/*LIGHTERBORDERBOTTOMCOLOREND*/;
|
||||
}
|
||||
|
||||
.modal-close .icon,
|
||||
.chev-right,
|
||||
.close-right,
|
||||
button.icon.nav-search,
|
||||
.member-button .join-text i,
|
||||
.external-member-button .join-text i,
|
||||
.external-follow-button .follow-text i,
|
||||
.qvitter-follow-button .follow-text i,
|
||||
#logo,
|
||||
.upload-cover-photo,
|
||||
.upload-avatar,
|
||||
.upload-background-image,
|
||||
button.shorten i,
|
||||
.reload-stream,
|
||||
.topbar .global-nav:before,
|
||||
.stream-item.notification.repeat .dogear,
|
||||
.stream-item.notification.like .dogear,
|
||||
.ostatus-link,
|
||||
.close-edit-profile-window {
|
||||
background-image: url("../../custom/shitposter-sprite2.png?v=41");
|
||||
background-size: 500px 1329px;
|
||||
}
|
||||
@media (max-width: 910px) {
|
||||
#search-query,
|
||||
.menu-container a,
|
||||
.menu-container a.current,
|
||||
.stream-selection.friends-timeline:after,
|
||||
.stream-selection.notifications:after,
|
||||
.stream-selection.my-timeline:after,
|
||||
.stream-selection.public-and-external-timeline:after,
|
||||
.stream-selection.public-timeline:after {
|
||||
background-image: url("../../custom/shitposter-sprite2.png?v=41");
|
||||
background-size: 500px 1329px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
<script type="text/javascript" src="https://shitposter.club/plugins/SensitiveContent/js/sensitivecontent.js"> </script>
|
1
test/fixtures/httpoison_mock/admin@mastdon.example.org.json
vendored
Normal file
1
test/fixtures/httpoison_mock/admin@mastdon.example.org.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","movedTo":"as:movedTo","Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji"}],"id":"http://mastodon.example.org/users/admin","type":"Person","following":"http://mastodon.example.org/users/admin/following","followers":"http://mastodon.example.org/users/admin/followers","inbox":"http://mastodon.example.org/users/admin/inbox","outbox":"http://mastodon.example.org/users/admin/outbox","preferredUsername":"admin","name":null,"summary":"\u003cp\u003e\u003c/p\u003e","url":"http://mastodon.example.org/@admin","manuallyApprovesFollowers":false,"publicKey":{"id":"http://mastodon.example.org/users/admin#main-key","owner":"http://mastodon.example.org/users/admin","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtc4Tir+3ADhSNF6VKrtW\nOU32T01w7V0yshmQei38YyiVwVvFu8XOP6ACchkdxbJ+C9mZud8qWaRJKVbFTMUG\nNX4+6Q+FobyuKrwN7CEwhDALZtaN2IPbaPd6uG1B7QhWorrY+yFa8f2TBM3BxnUy\nI4T+bMIZIEYG7KtljCBoQXuTQmGtuffO0UwJksidg2ffCF5Q+K//JfQagJ3UzrR+\nZXbKMJdAw4bCVJYs4Z5EhHYBwQWiXCyMGTd7BGlmMkY6Av7ZqHKC/owp3/0EWDNz\nNqF09Wcpr3y3e8nA10X40MJqp/wR+1xtxp+YGbq/Cj5hZGBG7etFOmIpVBrDOhry\nBwIDAQAB\n-----END PUBLIC KEY-----\n"},"endpoints":{"sharedInbox":"http://mastodon.example.org/inbox"},"icon":{"type":"Image","mediaType":"image/jpeg","url":"https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"},"image":{"type":"Image","mediaType":"image/png","url":"https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"}}
|
1
test/fixtures/httpoison_mock/hellpie.json
vendored
Normal file
1
test/fixtures/httpoison_mock/hellpie.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","movedTo":"as:movedTo","Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji"}],"id":"https://masto.quad.moe/users/_HellPie","type":"Person","following":"https://masto.quad.moe/users/_HellPie/following","followers":"https://masto.quad.moe/users/_HellPie/followers","inbox":"https://masto.quad.moe/users/_HellPie/inbox","outbox":"https://masto.quad.moe/users/_HellPie/outbox","preferredUsername":"_HellPie","name":"_HellPie","summary":"\u003cp\u003eAndroid (Java) Developer, Linux addict. Often an asshole. Usually mentally ill, sometimes just retarded.\u003c/p\u003e\u003cp\u003eGitHub: \u003ca href=\"https://github.com/HellPie\" rel=\"nofollow noopener\" target=\"_blank\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003egithub.com/HellPie\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e","url":"https://masto.quad.moe/@_HellPie","manuallyApprovesFollowers":false,"publicKey":{"id":"https://masto.quad.moe/users/_HellPie#main-key","owner":"https://masto.quad.moe/users/_HellPie","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1fIReYnqpap6e3sIskIx\ni7q130EvfkSOTBTBe01w3Xb/7/JwzWgkmSp+sK5s/ImO2oZb3ljmKZ3iTg4ETtVa\nCrT98/5p4Hlw/Oozb0kTx+tUazrucr023u8lTmn5sVgksKue59gPzKEuJJT1Te7H\nPJg2frz4QZWEY9nuygJoDaWgLvq1aa4oRfctlpo2C4d4oKRZFx2wtgeGVpahsikX\nKFBWuvEMFL2LUWb44BkvN6bTmXL9ryQY2oRsWn0yZHnTvFItq4vkFSNNe6sK13pM\nOHu1rVJrKg2hNVpBowds9YqZM8zP9F0GS7SEARbwPRCaAGLJGNwLjfJolJ/231eU\nKQIDAQAB\n-----END PUBLIC KEY-----\n"},"endpoints":{"sharedInbox":"https://masto.quad.moe/inbox"},"icon":{"type":"Image","mediaType":"image/png","url":"https://masto.quad.moe/system/accounts/avatars/000/012/255/original/39b907e6b169191d.png"},"image":{"type":"Image","mediaType":"image/png","url":"https://masto.quad.moe/system/accounts/headers/000/012/255/original/8d3ace0025bdda431e07230668303945.png"}}
|
1
test/fixtures/httpoison_mock/mayumayu.json
vendored
Normal file
1
test/fixtures/httpoison_mock/mayumayu.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","movedTo":"as:movedTo","Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji"}],"id":"https://mstdn.io/users/mayuutann","type":"Person","following":"https://mstdn.io/users/mayuutann/following","followers":"https://mstdn.io/users/mayuutann/followers","inbox":"https://mstdn.io/users/mayuutann/inbox","outbox":"https://mstdn.io/users/mayuutann/outbox","preferredUsername":"mayuutann","name":"Mayutan☕","summary":"\u003cp\u003eI enjoy programming as a hobby.\u003cbr /\u003eJava.Ruby. Practicing English . I love karaoke.\u003cbr /\u003eAichi Japan.\u003cbr /\u003eI\u0026apos;d be glad if you pointed out it when my English is unnatural.\u003c/p\u003e","url":"https://mstdn.io/@mayuutann","manuallyApprovesFollowers":false,"publicKey":{"id":"https://mstdn.io/users/mayuutann#main-key","owner":"https://mstdn.io/users/mayuutann","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvz+MncrdPxQ5R99g9m8X\nY6QO1WNOsCj0wXuDmCHJxXfJx5NFYgsYSX3y2UTzoHNcxZIwbSy24HlYR44cEygy\nimiysTk3o0pVquXhFQNDBXJkAkPfY+9O/gz1FTbwEUzFS1m9zmoQUesDjHEBXvpW\nHkNRdVThsDHotiMYjd+WYS09XjCYxhUHcwsnEFZ+55y1Uz6OveY2OZH+jTEluF+s\nLLTDopY37Ogniah0zVm7Q+/WPdbjOullpWh8s/c5fYGl5xMaS950l5r4gkPU7MVE\n4dGSd/v4pUAxlZrhbRHrKMD4c9cmxn9gJuqmW49ZmPzIeG+SaLnad6zh0BN9nveR\njQIDAQAB\n-----END PUBLIC KEY-----\n"},"endpoints":{"sharedInbox":"https://mstdn.io/inbox"},"icon":{"type":"Image","mediaType":"image/jpeg","url":"https://mstdn.io/system/accounts/avatars/000/021/478/original/40fe303d51305ba4.jpg"},"image":{"type":"Image","mediaType":"image/jpeg","url":"https://mstdn.io/system/accounts/headers/000/021/478/original/4e1e9b5e1f350abb.jpg"}}
|
1
test/fixtures/httpoison_mock/mayumayupost.json
vendored
Normal file
1
test/fixtures/httpoison_mock/mayumayupost.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","movedTo":"as:movedTo","Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji"}],"id":"https://mstdn.io/users/mayuutann/statuses/99568293732299394","type":"Note","summary":null,"content":"\u003cp\u003e\u003cspan class=\"h-card\"\u003e\u003ca href=\"https://shitposter.club/shpuld\" class=\"u-url mention\"\u003e@\u003cspan\u003eshpuld\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e \u003cspan class=\"h-card\"\u003e\u003ca href=\"https://testing.pleroma.lol/users/lain\" class=\"u-url mention\"\u003e@\u003cspan\u003elain\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e ポポポォォォ\u003c/p\u003e","inReplyTo":"https://shitposter.club/notice/7369654","published":"2018-02-22T09:26:31Z","url":"https://mstdn.io/@mayuutann/99568293732299394","attributedTo":"https://mstdn.io/users/mayuutann","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mstdn.io/users/mayuutann/followers","https://testing.pleroma.lol/users/lain","https://shitposter.club/user/5381"],"sensitive":false,"atomUri":"https://mstdn.io/users/mayuutann/statuses/99568293732299394","inReplyToAtomUri":"tag:shitposter.club,2018-02-22:noticeId=7369654:objectType=comment","conversation":"tag:shitposter.club,2018-02-22:objectType=thread:nonce=e5a7c72d60a9c0e4","attachment":[],"tag":[{"type":"Mention","href":"https://testing.pleroma.lol/users/lain","name":"@lain@testing.pleroma.lol"},{"type":"Mention","href":"https://shitposter.club/user/5381","name":"@shpuld@shitposter.club"}]}
|
1
test/fixtures/httpoison_mock/rye.json
vendored
Normal file
1
test/fixtures/httpoison_mock/rye.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","movedTo":"as:movedTo","Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji"}],"id":"https://niu.moe/users/rye","type":"Person","following":"https://niu.moe/users/rye/following","followers":"https://niu.moe/users/rye/followers","inbox":"https://niu.moe/users/rye/inbox","outbox":"https://niu.moe/users/rye/outbox","preferredUsername":"rye","name":"♡ rye ♡","summary":"\u003cp\u003elettuce club champion\u003c/p\u003e\u003cp\u003eicon by gomigomipomi\u003c/p\u003e","url":"https://niu.moe/@rye","manuallyApprovesFollowers":false,"publicKey":{"id":"https://niu.moe/users/rye#main-key","owner":"https://niu.moe/users/rye","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA83uRWjCFO35FwfA38mzv\nEL0TUaXB7+2hYvPwNrn1WY6me5DRbqB5zzMrzWMGr0HSooqNqEYBafGsmVTWUqIk\nKM9ehtIBraJI+mT5X7DPR3LrXOJF4a9EEslg8XvAk8MN9IrAhm6UljnvB67RtDcA\nTNB01VWy9yWnxFRtz9o/EMoBPyw5giOaXE2ibVNP8lQIqGKuuBKPzPjSJygdvQ5q\nxfow2z1TpKRqdsNDqn4n6U6zCXYTzkr0J71/tGw7fsgfv78l0Wjrc7EcuBk74OaG\nC65UDiu3X4Q6kxCfCEhPSfuwLN+UZkzxcn6goWR0iYpWs57+4tFKu9nJYP4QJ0K9\nTwIDAQAB\n-----END PUBLIC KEY-----\n"},"endpoints":{"sharedInbox":"https://niu.moe/inbox"},"icon":{"type":"Image","mediaType":"image/jpeg","url":"https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"},"image":{"type":"Image","mediaType":"image/png","url":"https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"}}
|
438
test/fixtures/httpoison_mock/spc_5381.atom
vendored
Normal file
438
test/fixtures/httpoison_mock/spc_5381.atom
vendored
Normal file
|
@ -0,0 +1,438 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:georss="http://www.georss.org/georss" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:media="http://purl.org/syndication/atommedia" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:statusnet="http://status.net/schema/api/1/">
|
||||
<generator uri="https://gnu.io/social" version="1.2.0-beta4">GNU social</generator>
|
||||
<id>https://shitposter.club/api/statuses/user_timeline/5381.atom</id>
|
||||
<title>shpuld timeline</title>
|
||||
<subtitle>Updates from shpuld on Shitposter Club!</subtitle>
|
||||
<logo>https://shitposter.club/avatar/5381-96-20171230093854.png</logo>
|
||||
<updated>2018-02-23T13:42:22+00:00</updated>
|
||||
<author>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
|
||||
<uri>https://shitposter.club/user/5381</uri>
|
||||
<name>shpuld</name>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/shpuld"/>
|
||||
<link rel="avatar" type="image/png" media:width="864" media:height="864" href="https://shitposter.club/avatar/5381-original-20171230093854.png"/>
|
||||
<link rel="avatar" type="image/png" media:width="96" media:height="96" href="https://shitposter.club/avatar/5381-96-20171230093854.png"/>
|
||||
<link rel="avatar" type="image/png" media:width="48" media:height="48" href="https://shitposter.club/avatar/5381-48-20171230093854.png"/>
|
||||
<link rel="avatar" type="image/png" media:width="24" media:height="24" href="https://shitposter.club/avatar/5381-24-20171230093900.png"/>
|
||||
<poco:preferredUsername>shpuld</poco:preferredUsername>
|
||||
<poco:displayName>shp</poco:displayName>
|
||||
<followers url="https://shitposter.club/shpuld/subscribers"></followers>
|
||||
<statusnet:profile_info local_id="5381"></statusnet:profile_info>
|
||||
</author>
|
||||
<link href="https://shitposter.club/shpuld" rel="alternate" type="text/html"/>
|
||||
<link href="https://shitposter.club/main/sup" rel="http://api.friendfeed.com/2008/03#sup" type="application/json"/>
|
||||
<link href="https://shitposter.club/api/statuses/user_timeline/5381.atom?max_id=7387342" rel="next" type="application/atom+xml"/>
|
||||
<link href="https://shitposter.club/main/push/hub" rel="hub"/>
|
||||
<link href="https://shitposter.club/main/salmon/user/5381" rel="salmon"/>
|
||||
<link href="https://shitposter.club/main/salmon/user/5381" rel="http://salmon-protocol.org/ns/salmon-replies"/>
|
||||
<link href="https://shitposter.club/main/salmon/user/5381" rel="http://salmon-protocol.org/ns/salmon-mention"/>
|
||||
<link href="https://shitposter.club/api/statuses/user_timeline/5381.atom" rel="self" type="application/atom+xml"/>
|
||||
<entry>
|
||||
<id>tag:shitposter.club,2018-02-23:fave:5381:comment:7387801:2018-02-23T13:39:40+00:00</id>
|
||||
<title>Favorite</title>
|
||||
<content type="html">shpuld favorited something by mayuutann: <p><span class="h-card"><a href="https://freezepeach.xyz/hakui" class="u-url mention">@<span>hakui</span></a></span> <span class="h-card"><a href="https://gs.smuglo.li/histoire" class="u-url mention">@<span>histoire</span></a></span> <span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> <a href="https://mstdn.io/media/_Ee-x91XN0udpfZVO_U" rel="nofollow"><span class="invisible">https://</span><span class="ellipsis">mstdn.io/media/_Ee-x91XN0udpfZ</span><span class="invisible">VO_U</span></a></p></content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387804"/>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/favorite</activity:verb>
|
||||
<published>2018-02-23T13:39:40+00:00</published>
|
||||
<updated>2018-02-23T13:39:40+00:00</updated>
|
||||
<activity:object>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||
<id>https://mstdn.io/users/mayuutann/statuses/99574950785668071</id>
|
||||
<title>New comment by mayuutann</title>
|
||||
<content type="html"><p><span class="h-card"><a href="https://freezepeach.xyz/hakui" class="u-url mention">@<span>hakui</span></a></span> <span class="h-card"><a href="https://gs.smuglo.li/histoire" class="u-url mention">@<span>histoire</span></a></span> <span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> <a href="https://mstdn.io/media/_Ee-x91XN0udpfZVO_U" rel="nofollow"><span class="invisible">https://</span><span class="ellipsis">mstdn.io/media/_Ee-x91XN0udpfZ</span><span class="invisible">VO_U</span></a></p></content>
|
||||
<link rel="alternate" type="text/html" href="https://mstdn.io/@mayuutann/99574950785668071"/>
|
||||
<status_net notice_id="7387801"></status_net>
|
||||
</activity:object>
|
||||
<thr:in-reply-to ref="https://mstdn.io/users/mayuutann/statuses/99574950785668071" href="https://mstdn.io/@mayuutann/99574950785668071"></thr:in-reply-to>
|
||||
<link rel="related" href="https://mstdn.io/@mayuutann/99574950785668071"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4389848"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4389848" local_id="4389848" ref="https://freezepeach.xyz/conversation/4182511">https://freezepeach.xyz/conversation/4182511</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387804.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387804.atom"/>
|
||||
<statusnet:notice_info local_id="7387804" source="unknown"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||
<id>tag:shitposter.club,2018-02-23:noticeId=7387723:objectType=comment</id>
|
||||
<title>New comment by shpuld</title>
|
||||
<content type="html">@<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> @<a href="https://pleroma.soykaf.com/users/lain" class="h-card mention" title="&#x2468; lain &#x2468;">lain</a> how naive~</content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387723"/>
|
||||
<status_net notice_id="7387723"></status_net>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<published>2018-02-23T13:30:15+00:00</published>
|
||||
<updated>2018-02-23T13:30:15+00:00</updated>
|
||||
<thr:in-reply-to ref="tag:freezepeach.xyz,2018-02-23:noticeId=6451587:objectType=comment" href="https://freezepeach.xyz/notice/6451587"></thr:in-reply-to>
|
||||
<link rel="related" href="https://freezepeach.xyz/notice/6451587"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4389967"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4389967" local_id="4389967" ref="tag:shitposter.club,2018-02-23:objectType=thread:nonce=2f09acf104aebfe3">tag:shitposter.club,2018-02-23:objectType=thread:nonce=2f09acf104aebfe3</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://freezepeach.xyz/user/3458"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://pleroma.soykaf.com/users/lain"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387723.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387723.atom"/>
|
||||
<statusnet:notice_info local_id="7387723" source="Pleroma FE"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||
<id>tag:shitposter.club,2018-02-23:noticeId=7387703:objectType=comment</id>
|
||||
<title>New comment by shpuld</title>
|
||||
<content type="html">@<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> @<a href="https://pleroma.soykaf.com/users/lain" class="h-card mention" title="&#x2468; lain &#x2468;">lain</a> you expect anyone to believe that??</content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387703"/>
|
||||
<status_net notice_id="7387703"></status_net>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<published>2018-02-23T13:28:08+00:00</published>
|
||||
<updated>2018-02-23T13:28:08+00:00</updated>
|
||||
<thr:in-reply-to ref="tag:freezepeach.xyz,2018-02-23:noticeId=6451569:objectType=comment" href="https://freezepeach.xyz/notice/6451569"></thr:in-reply-to>
|
||||
<link rel="related" href="https://freezepeach.xyz/notice/6451569"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4389967"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4389967" local_id="4389967" ref="tag:shitposter.club,2018-02-23:objectType=thread:nonce=2f09acf104aebfe3">tag:shitposter.club,2018-02-23:objectType=thread:nonce=2f09acf104aebfe3</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://freezepeach.xyz/user/3458"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://pleroma.soykaf.com/users/lain"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387703.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387703.atom"/>
|
||||
<statusnet:notice_info local_id="7387703" source="Pleroma FE"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||
<id>tag:shitposter.club,2018-02-23:noticeId=7387639:objectType=comment</id>
|
||||
<title>New comment by shpuld</title>
|
||||
<content type="html">@<a href="https://mstdn.io/users/mayuutann" class="h-card mention" title="Mayutan&#x2615;">mayuutann</a> @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> pacyuri!! <a href="https://shitposter.club/file/eea140be45df3f993c4533026bf9a78fe8facd296d2fa0c6d02b2e347c5dc30e.jpg" title="https://shitposter.club/file/eea140be45df3f993c4533026bf9a78fe8facd296d2fa0c6d02b2e347c5dc30e.jpg" class="attachment" id="attachment-1589462" rel="nofollow external">https://shitposter.club/attachment/1589462</a></content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387639"/>
|
||||
<status_net notice_id="7387639"></status_net>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<published>2018-02-23T13:20:38+00:00</published>
|
||||
<updated>2018-02-23T13:20:38+00:00</updated>
|
||||
<thr:in-reply-to ref="https://mstdn.io/users/mayuutann/statuses/99574870416888767" href="https://mstdn.io/@mayuutann/99574870416888767"></thr:in-reply-to>
|
||||
<link rel="related" href="https://mstdn.io/@mayuutann/99574870416888767"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4390261"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4390261" local_id="4390261" ref="https://freezepeach.xyz/conversation/4183220">https://freezepeach.xyz/conversation/4183220</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://freezepeach.xyz/user/3458"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://mstdn.io/users/mayuutann"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="enclosure" href="https://shitposter.club/file/eea140be45df3f993c4533026bf9a78fe8facd296d2fa0c6d02b2e347c5dc30e.jpg" type="image/jpeg" length="42186"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387639.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387639.atom"/>
|
||||
<statusnet:notice_info local_id="7387639" source="Pleroma FE"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||
<id>tag:shitposter.club,2018-02-23:noticeId=7387611:objectType=comment</id>
|
||||
<title>New comment by shpuld</title>
|
||||
<content type="html">@<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> why is pacyu eating a pizza so cute</content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387611"/>
|
||||
<status_net notice_id="7387611"></status_net>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<published>2018-02-23T13:18:07+00:00</published>
|
||||
<updated>2018-02-23T13:18:07+00:00</updated>
|
||||
<thr:in-reply-to ref="tag:freezepeach.xyz,2018-02-23:noticeId=6451402:objectType=comment" href="https://freezepeach.xyz/notice/6451402"></thr:in-reply-to>
|
||||
<link rel="related" href="https://freezepeach.xyz/notice/6451402"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4390261"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4390261" local_id="4390261" ref="https://freezepeach.xyz/conversation/4183220">https://freezepeach.xyz/conversation/4183220</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://freezepeach.xyz/user/3458"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387611.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387611.atom"/>
|
||||
<statusnet:notice_info local_id="7387611" source="Pleroma FE"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<id>tag:shitposter.club,2018-02-23:fave:5381:comment:7387600:2018-02-23T13:17:52+00:00</id>
|
||||
<title>Favorite</title>
|
||||
<content type="html">shpuld favorited something by mayuutann: <p><span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> <span class="h-card"><a href="https://gs.smuglo.li/histoire" class="u-url mention">@<span>histoire</span></a></span> <span class="h-card"><a href="https://freezepeach.xyz/hakui" class="u-url mention">@<span>hakui</span></a></span> pichu! <a href="https://mstdn.io/media/Crv5eubz1KO0dgBEulI" rel="nofollow"><span class="invisible">https://</span><span class="ellipsis">mstdn.io/media/Crv5eubz1KO0dgB</span><span class="invisible">EulI</span></a></p></content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387606"/>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/favorite</activity:verb>
|
||||
<published>2018-02-23T13:17:52+00:00</published>
|
||||
<updated>2018-02-23T13:17:52+00:00</updated>
|
||||
<activity:object>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||
<id>https://mstdn.io/users/mayuutann/statuses/99574863865459283</id>
|
||||
<title>New comment by mayuutann</title>
|
||||
<content type="html"><p><span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> <span class="h-card"><a href="https://gs.smuglo.li/histoire" class="u-url mention">@<span>histoire</span></a></span> <span class="h-card"><a href="https://freezepeach.xyz/hakui" class="u-url mention">@<span>hakui</span></a></span> pichu! <a href="https://mstdn.io/media/Crv5eubz1KO0dgBEulI" rel="nofollow"><span class="invisible">https://</span><span class="ellipsis">mstdn.io/media/Crv5eubz1KO0dgB</span><span class="invisible">EulI</span></a></p></content>
|
||||
<link rel="alternate" type="text/html" href="https://mstdn.io/@mayuutann/99574863865459283"/>
|
||||
<status_net notice_id="7387600"></status_net>
|
||||
</activity:object>
|
||||
<thr:in-reply-to ref="https://mstdn.io/users/mayuutann/statuses/99574863865459283" href="https://mstdn.io/@mayuutann/99574863865459283"></thr:in-reply-to>
|
||||
<link rel="related" href="https://mstdn.io/@mayuutann/99574863865459283"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4389848"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4389848" local_id="4389848" ref="https://freezepeach.xyz/conversation/4182511">https://freezepeach.xyz/conversation/4182511</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387606.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387606.atom"/>
|
||||
<statusnet:notice_info local_id="7387606" source="unknown"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<id>tag:shitposter.club,2018-02-23:fave:5381:comment:7387544:2018-02-23T13:12:43+00:00</id>
|
||||
<title>Favorite</title>
|
||||
<content type="html">shpuld favorited something by mayuutann: <p><span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> wa~~i!! :blobcheer:</p></content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387557"/>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/favorite</activity:verb>
|
||||
<published>2018-02-23T13:12:43+00:00</published>
|
||||
<updated>2018-02-23T13:12:43+00:00</updated>
|
||||
<activity:object>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||
<id>https://mstdn.io/users/mayuutann/statuses/99574840290947233</id>
|
||||
<title>New comment by mayuutann</title>
|
||||
<content type="html"><p><span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> wa~~i!! :blobcheer:</p></content>
|
||||
<link rel="alternate" type="text/html" href="https://mstdn.io/@mayuutann/99574840290947233"/>
|
||||
<status_net notice_id="7387544"></status_net>
|
||||
</activity:object>
|
||||
<thr:in-reply-to ref="https://mstdn.io/users/mayuutann/statuses/99574840290947233" href="https://mstdn.io/@mayuutann/99574840290947233"></thr:in-reply-to>
|
||||
<link rel="related" href="https://mstdn.io/@mayuutann/99574840290947233"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4390030"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4390030" local_id="4390030" ref="tag:shitposter.club,2018-02-23:objectType=thread:nonce=d05e2b056274c5ab">tag:shitposter.club,2018-02-23:objectType=thread:nonce=d05e2b056274c5ab</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387557.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387557.atom"/>
|
||||
<statusnet:notice_info local_id="7387557" source="unknown"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||
<id>tag:shitposter.club,2018-02-23:noticeId=7387555:objectType=comment</id>
|
||||
<title>New comment by shpuld</title>
|
||||
<content type="html">@<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> more!!</content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387555"/>
|
||||
<status_net notice_id="7387555"></status_net>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<published>2018-02-23T13:12:23+00:00</published>
|
||||
<updated>2018-02-23T13:12:23+00:00</updated>
|
||||
<thr:in-reply-to ref="tag:freezepeach.xyz,2018-02-23:noticeId=6451332:objectType=note" href="https://freezepeach.xyz/notice/6451332"></thr:in-reply-to>
|
||||
<link rel="related" href="https://freezepeach.xyz/notice/6451332"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4390261"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4390261" local_id="4390261" ref="https://freezepeach.xyz/conversation/4183220">https://freezepeach.xyz/conversation/4183220</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://freezepeach.xyz/user/3458"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387555.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387555.atom"/>
|
||||
<statusnet:notice_info local_id="7387555" source="Pleroma FE"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<id>tag:shitposter.club,2018-02-23:fave:5381:note:7387537:2018-02-23T13:12:19+00:00</id>
|
||||
<title>Favorite</title>
|
||||
<content type="html">shpuld favorited something by hakui: you have pacyupacyu'd for: 45 minutes 03 seconds</content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387553"/>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/favorite</activity:verb>
|
||||
<published>2018-02-23T13:12:19+00:00</published>
|
||||
<updated>2018-02-23T13:12:19+00:00</updated>
|
||||
<activity:object>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||
<id>tag:freezepeach.xyz,2018-02-23:noticeId=6451332:objectType=note</id>
|
||||
<title>New note by hakui</title>
|
||||
<content type="html">you have pacyupacyu'd for: 45 minutes 03 seconds</content>
|
||||
<link rel="alternate" type="text/html" href="https://freezepeach.xyz/notice/6451332"/>
|
||||
<status_net notice_id="7387537"></status_net>
|
||||
</activity:object>
|
||||
<thr:in-reply-to ref="tag:freezepeach.xyz,2018-02-23:noticeId=6451332:objectType=note" href="https://freezepeach.xyz/notice/6451332"></thr:in-reply-to>
|
||||
<link rel="related" href="https://freezepeach.xyz/notice/6451332"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4390261"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4390261" local_id="4390261" ref="https://freezepeach.xyz/conversation/4183220">https://freezepeach.xyz/conversation/4183220</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387553.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387553.atom"/>
|
||||
<statusnet:notice_info local_id="7387553" source="unknown"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||
<id>tag:shitposter.club,2018-02-23:noticeId=7387539:objectType=comment</id>
|
||||
<title>New comment by shpuld</title>
|
||||
<content type="html">@<a href="https://mstdn.io/users/mayuutann" class="h-card mention" title="Mayutan&#x2615;">mayuutann</a> ndndnd~</content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387539"/>
|
||||
<status_net notice_id="7387539"></status_net>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<published>2018-02-23T13:11:04+00:00</published>
|
||||
<updated>2018-02-23T13:11:04+00:00</updated>
|
||||
<thr:in-reply-to ref="https://mstdn.io/users/mayuutann/statuses/99574837619821505" href="https://mstdn.io/@mayuutann/99574837619821505"></thr:in-reply-to>
|
||||
<link rel="related" href="https://mstdn.io/@mayuutann/99574837619821505"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4390030"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4390030" local_id="4390030" ref="tag:shitposter.club,2018-02-23:objectType=thread:nonce=d05e2b056274c5ab">tag:shitposter.club,2018-02-23:objectType=thread:nonce=d05e2b056274c5ab</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://mstdn.io/users/mayuutann"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387539.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387539.atom"/>
|
||||
<statusnet:notice_info local_id="7387539" source="Pleroma FE"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||
<id>tag:shitposter.club,2018-02-23:noticeId=7387518:objectType=comment</id>
|
||||
<title>New comment by shpuld</title>
|
||||
<content type="html">@<a href="https://mstdn.io/users/mayuutann" class="h-card mention" title="Mayutan&#x2615;">mayuutann</a> well done! mayumayu is so energetic</content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387518"/>
|
||||
<status_net notice_id="7387518"></status_net>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<published>2018-02-23T13:08:50+00:00</published>
|
||||
<updated>2018-02-23T13:08:50+00:00</updated>
|
||||
<thr:in-reply-to ref="https://mstdn.io/users/mayuutann/statuses/99574826506801503" href="https://mstdn.io/@mayuutann/99574826506801503"></thr:in-reply-to>
|
||||
<link rel="related" href="https://mstdn.io/@mayuutann/99574826506801503"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4390030"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4390030" local_id="4390030" ref="tag:shitposter.club,2018-02-23:objectType=thread:nonce=d05e2b056274c5ab">tag:shitposter.club,2018-02-23:objectType=thread:nonce=d05e2b056274c5ab</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://mstdn.io/users/mayuutann"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387518.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387518.atom"/>
|
||||
<statusnet:notice_info local_id="7387518" source="Pleroma FE"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<id>tag:shitposter.club,2018-02-23:fave:5381:note:7387503:2018-02-23T13:08:00+00:00</id>
|
||||
<title>Favorite</title>
|
||||
<content type="html">shpuld favorited something by mayuutann: <p>done with FIGURE MAT!!<br /> (Posted with IFTTT)</p></content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387511"/>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/favorite</activity:verb>
|
||||
<published>2018-02-23T13:08:00+00:00</published>
|
||||
<updated>2018-02-23T13:08:00+00:00</updated>
|
||||
<activity:object>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||
<id>https://mstdn.io/users/mayuutann/statuses/99574825526201897</id>
|
||||
<title>New note by mayuutann</title>
|
||||
<content type="html"><p>done with FIGURE MAT!!<br /> (Posted with IFTTT)</p></content>
|
||||
<link rel="alternate" type="text/html" href="https://mstdn.io/@mayuutann/99574825526201897"/>
|
||||
<status_net notice_id="7387503"></status_net>
|
||||
</activity:object>
|
||||
<thr:in-reply-to ref="https://mstdn.io/users/mayuutann/statuses/99574825526201897" href="https://mstdn.io/@mayuutann/99574825526201897"></thr:in-reply-to>
|
||||
<link rel="related" href="https://mstdn.io/@mayuutann/99574825526201897"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4390240"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4390240" local_id="4390240" ref="tag:shitposter.club,2018-02-23:objectType=thread:nonce=c6aaa9b91e8d242f">tag:shitposter.club,2018-02-23:objectType=thread:nonce=c6aaa9b91e8d242f</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387511.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387511.atom"/>
|
||||
<statusnet:notice_info local_id="7387511" source="unknown"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||
<id>tag:shitposter.club,2018-02-23:noticeId=7387486:objectType=comment</id>
|
||||
<title>New comment by shpuld</title>
|
||||
<content type="html">@<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> @<a href="https://a.weirder.earth/users/mutstd" class="h-card mention" title="Mutant Standard">mutstd</a> @<a href="https://donphan.social/users/Siphonay" class="h-card mention" title="Siphonay">siphonay</a> jokes on you I'm oppressively shitposting myself</content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387486"/>
|
||||
<status_net notice_id="7387486"></status_net>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<published>2018-02-23T13:05:44+00:00</published>
|
||||
<updated>2018-02-23T13:05:44+00:00</updated>
|
||||
<thr:in-reply-to ref="tag:freezepeach.xyz,2018-02-23:noticeId=6451272:objectType=comment" href="https://freezepeach.xyz/notice/6451272"></thr:in-reply-to>
|
||||
<link rel="related" href="https://freezepeach.xyz/notice/6451272"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4389665"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4389665" local_id="4389665" ref="tag:shitposter.club,2018-02-23:objectType=thread:nonce=5d306467336c9661">tag:shitposter.club,2018-02-23:objectType=thread:nonce=5d306467336c9661</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://freezepeach.xyz/user/3458"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://a.weirder.earth/users/mutstd"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://donphan.social/users/Siphonay"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387486.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387486.atom"/>
|
||||
<statusnet:notice_info local_id="7387486" source="Pleroma FE"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||
<id>tag:shitposter.club,2018-02-23:noticeId=7387466:objectType=comment</id>
|
||||
<title>New comment by shpuld</title>
|
||||
<content type="html">@<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> @<a href="https://a.weirder.earth/users/mutstd" class="h-card mention" title="Mutant Standard">mutstd</a> @<a href="https://donphan.social/users/Siphonay" class="h-card mention" title="Siphonay">siphonay</a> how does it feel being hostile</content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387466"/>
|
||||
<status_net notice_id="7387466"></status_net>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<published>2018-02-23T13:04:10+00:00</published>
|
||||
<updated>2018-02-23T13:04:10+00:00</updated>
|
||||
<thr:in-reply-to ref="tag:freezepeach.xyz,2018-02-23:noticeId=6451260:objectType=comment" href="https://freezepeach.xyz/notice/6451260"></thr:in-reply-to>
|
||||
<link rel="related" href="https://freezepeach.xyz/notice/6451260"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4389665"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4389665" local_id="4389665" ref="tag:shitposter.club,2018-02-23:objectType=thread:nonce=5d306467336c9661">tag:shitposter.club,2018-02-23:objectType=thread:nonce=5d306467336c9661</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://freezepeach.xyz/user/3458"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://a.weirder.earth/users/mutstd"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://donphan.social/users/Siphonay"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387466.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387466.atom"/>
|
||||
<statusnet:notice_info local_id="7387466" source="Pleroma FE"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||
<id>tag:shitposter.club,2018-02-23:noticeId=7387459:objectType=comment</id>
|
||||
<title>New comment by shpuld</title>
|
||||
<content type="html">@<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> gorogoro</content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387459"/>
|
||||
<status_net notice_id="7387459"></status_net>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<published>2018-02-23T13:03:32+00:00</published>
|
||||
<updated>2018-02-23T13:03:32+00:00</updated>
|
||||
<thr:in-reply-to ref="tag:freezepeach.xyz,2018-02-23:noticeId=6451248:objectType=comment" href="https://freezepeach.xyz/notice/6451248"></thr:in-reply-to>
|
||||
<link rel="related" href="https://freezepeach.xyz/notice/6451248"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4389271"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4389271" local_id="4389271" ref="https://freezepeach.xyz/conversation/4181784">https://freezepeach.xyz/conversation/4181784</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://freezepeach.xyz/user/3458"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387459.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387459.atom"/>
|
||||
<statusnet:notice_info local_id="7387459" source="Pleroma FE"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||
<id>tag:shitposter.club,2018-02-23:noticeId=7387432:objectType=comment</id>
|
||||
<title>New comment by shpuld</title>
|
||||
<content type="html">@<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> ndnd</content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387432"/>
|
||||
<status_net notice_id="7387432"></status_net>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<published>2018-02-23T13:02:05+00:00</published>
|
||||
<updated>2018-02-23T13:02:05+00:00</updated>
|
||||
<thr:in-reply-to ref="tag:freezepeach.xyz,2018-02-23:noticeId=6451204:objectType=comment" href="https://freezepeach.xyz/notice/6451204"></thr:in-reply-to>
|
||||
<link rel="related" href="https://freezepeach.xyz/notice/6451204"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4389271"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4389271" local_id="4389271" ref="https://freezepeach.xyz/conversation/4181784">https://freezepeach.xyz/conversation/4181784</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://freezepeach.xyz/user/3458"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387432.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387432.atom"/>
|
||||
<statusnet:notice_info local_id="7387432" source="Pleroma FE"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||
<id>tag:shitposter.club,2018-02-23:noticeId=7387367:objectType=note</id>
|
||||
<title>New note by shpuld</title>
|
||||
<content type="html">dear diary: I'm trying to do work but I can only think of tenshi eating a corndog</content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387367"/>
|
||||
<status_net notice_id="7387367"></status_net>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<published>2018-02-23T12:56:03+00:00</published>
|
||||
<updated>2018-02-23T12:56:03+00:00</updated>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4390142"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4390142" local_id="4390142" ref="tag:shitposter.club,2018-02-23:objectType=thread:nonce=57f316da416743fc">tag:shitposter.club,2018-02-23:objectType=thread:nonce=57f316da416743fc</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387367.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387367.atom"/>
|
||||
<statusnet:notice_info local_id="7387367" source="Pleroma FE"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||
<id>tag:shitposter.club,2018-02-23:noticeId=7387354:objectType=note</id>
|
||||
<title>New note by shpuld</title>
|
||||
<content type="html">jesus christ it's such a fridey at work</content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387354"/>
|
||||
<status_net notice_id="7387354"></status_net>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<published>2018-02-23T12:53:50+00:00</published>
|
||||
<updated>2018-02-23T12:53:50+00:00</updated>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4390131"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4390131" local_id="4390131" ref="tag:shitposter.club,2018-02-23:objectType=thread:nonce=c05eb5e91bdcbdb7">tag:shitposter.club,2018-02-23:objectType=thread:nonce=c05eb5e91bdcbdb7</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387354.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387354.atom"/>
|
||||
<statusnet:notice_info local_id="7387354" source="Pleroma FE"></statusnet:notice_info>
|
||||
</entry>
|
||||
<entry>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||
<id>tag:shitposter.club,2018-02-23:noticeId=7387343:objectType=comment</id>
|
||||
<title>New comment by shpuld</title>
|
||||
<content type="html">@<a href="https://gs.smuglo.li/user/589" class="h-card mention" title="&#x16DE;&#x16A9;&#x16B3;&#x16C1;&#x16DE;&#x16A9;&#x16B3;&#x16C1;">dokidoki</a> give them free upgrades to krokodil</content>
|
||||
<link rel="alternate" type="text/html" href="https://shitposter.club/notice/7387343"/>
|
||||
<status_net notice_id="7387343"></status_net>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<published>2018-02-23T12:53:15+00:00</published>
|
||||
<updated>2018-02-23T12:53:15+00:00</updated>
|
||||
<thr:in-reply-to ref="tag:gs.smuglo.li,2018-02-23:noticeId=6201061:objectType=note" href="https://gs.smuglo.li/notice/6201061"></thr:in-reply-to>
|
||||
<link rel="related" href="https://gs.smuglo.li/notice/6201061"/>
|
||||
<link rel="ostatus:conversation" href="https://shitposter.club/conversation/4390117"/>
|
||||
<ostatus:conversation href="https://shitposter.club/conversation/4390117" local_id="4390117" ref="https://gs.smuglo.li/conversation/3934774">https://gs.smuglo.li/conversation/3934774</ostatus:conversation>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://gs.smuglo.li/user/589"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387343.atom"/>
|
||||
<link rel="edit" type="application/atom+xml" href="https://shitposter.club/api/statuses/show/7387343.atom"/>
|
||||
<statusnet:notice_info local_id="7387343" source="Pleroma FE"></statusnet:notice_info>
|
||||
</entry>
|
||||
</feed>
|
20
test/fixtures/httpoison_mock/spc_5381_xrd.xml
vendored
Normal file
20
test/fixtures/httpoison_mock/spc_5381_xrd.xml
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
||||
<Subject>https://shitposter.club/user/5381</Subject>
|
||||
<Alias>acct:shpuld@shitposter.club</Alias>
|
||||
<Alias>https://shitposter.club/shpuld</Alias>
|
||||
<Alias>https://shitposter.club/index.php/user/5381</Alias>
|
||||
<Alias>https://shitposter.club/index.php/shpuld</Alias>
|
||||
<Link rel="http://webfinger.net/rel/profile-page" type="text/html" href="https://shitposter.club/shpuld"/>
|
||||
<Link rel="http://gmpg.org/xfn/11" type="text/html" href="https://shitposter.club/shpuld"/>
|
||||
<Link rel="describedby" type="application/rdf+xml" href="https://shitposter.club/shpuld/foaf"/>
|
||||
<Link rel="http://apinamespace.org/atom" type="application/atomsvc+xml" href="https://shitposter.club/api/statusnet/app/service/shpuld.xml"/>
|
||||
<Link rel="http://apinamespace.org/twitter" href="https://shitposter.club/api/"/>
|
||||
<Link rel="http://specs.openid.net/auth/2.0/provider" href="https://shitposter.club/shpuld"/>
|
||||
<Link rel="http://schemas.google.com/g/2010#updates-from" type="application/atom+xml" href="https://shitposter.club/api/statuses/user_timeline/5381.atom"/>
|
||||
<Link rel="magic-public-key" href="data:application/magic-public-key,RSA.pkJ_xCKxFzcOKuKPKFhUTkWLWyWAIRDS8onxRLxVvxITQAkHIO1Rl9FS_1DAT3MK_wBcbzXm1TwlVOQFY5I2zrZQGxUvGDUlqcsf9sQyQaNvVVoU83nAV2w9bQZ-GlaLCMHWKN4yBBCTPfu9J6XbItxbHhJg5ub8z5drDF45te8=.AQAB"/>
|
||||
<Link rel="salmon" href="https://shitposter.club/main/salmon/user/5381"/>
|
||||
<Link rel="http://salmon-protocol.org/ns/salmon-replies" href="https://shitposter.club/main/salmon/user/5381"/>
|
||||
<Link rel="http://salmon-protocol.org/ns/salmon-mention" href="https://shitposter.club/main/salmon/user/5381"/>
|
||||
<Link rel="http://ostatus.org/schema/1.0/subscribe" template="https://shitposter.club/main/ostatussub?profile={uri}"/>
|
||||
</XRD>
|
34
test/fixtures/mastodon-accept-activity.json
vendored
Normal file
34
test/fixtures/mastodon-accept-activity.json
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"type": "Accept",
|
||||
"signature": {
|
||||
"type": "RsaSignature2017",
|
||||
"signatureValue": "rBzK4Kqhd4g7HDS8WE5oRbWQb2R+HF/6awbUuMWhgru/xCODT0SJWSri0qWqEO4fPcpoUyz2d25cw6o+iy9wiozQb3hQNnu69AR+H5Mytc06+g10KCHexbGhbAEAw/7IzmeXELHUbaqeduaDIbdt1zw4RkwLXdqgQcGXTJ6ND1wM3WMHXQCK1m0flasIXFoBxpliPAGiElV8s0+Ltuh562GvflG3kB3WO+j+NaR0ZfG5G9N88xMj9UQlCKit5gpAE5p6syUsCU2WGBHywTumv73i3OVTIFfq+P9AdMsRuzw1r7zoKEsthW4aOzLQDi01ZjvdBz8zH6JnjDU7SMN/Ig==",
|
||||
"creator": "http://mastodon.example.org/users/admin#main-key",
|
||||
"created": "2018-02-17T14:36:41Z"
|
||||
},
|
||||
"object": {
|
||||
"type": "Follow",
|
||||
"object": "http://mastodon.example.org/users/admin",
|
||||
"id": "http://localtesting.pleroma.lol/users/lain#follows/4",
|
||||
"actor": "http://localtesting.pleroma.lol/users/lain"
|
||||
},
|
||||
"nickname": "lain",
|
||||
"id": "http://mastodon.example.org/users/admin#accepts/follows/4",
|
||||
"actor": "http://mastodon.example.org/users/admin",
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"sensitive": "as:sensitive",
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"movedTo": "as:movedTo",
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||
"conversation": "ostatus:conversation",
|
||||
"atomUri": "ostatus:atomUri",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"Emoji": "toot:Emoji"
|
||||
}
|
||||
]
|
||||
}
|
37
test/fixtures/mastodon-announce.json
vendored
Normal file
37
test/fixtures/mastodon-announce.json
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"type": "Announce",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"signature": {
|
||||
"type": "RsaSignature2017",
|
||||
"signatureValue": "T95DRE0eAligvMuRMkQA01lsoz2PKi4XXF+cyZ0BqbrO12p751TEWTyyRn5a+HH0e4kc77EUhQVXwMq80WAYDzHKVUTf2XBJPBa68vl0j6RXw3+HK4ef5hR4KWFNBU34yePS7S1fEmc1mTG4Yx926wtmZwDpEMTp1CXOeVEjCYzmdyHpepPPH2ZZettiacmPRSqBLPGWZoot7kH/SioIdnrMGY0I7b+rqkIdnnEcdhu9N1BKPEO9Sr+KmxgAUiidmNZlbBXX6gCxp8BiIdH4ABsIcwoDcGNkM5EmWunGW31LVjsEQXhH5c1Wly0ugYYPCg/0eHLNBOhKkY/teSM8Lg==",
|
||||
"creator": "http://mastodon.example.org/users/admin#main-key",
|
||||
"created": "2018-02-17T19:39:15Z"
|
||||
},
|
||||
"published": "2018-02-17T19:39:15Z",
|
||||
"object": "http://mastodon.example.org/@admin/99541947525187367",
|
||||
"id": "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity",
|
||||
"cc": [
|
||||
"http://mastodon.example.org/users/admin",
|
||||
"http://mastodon.example.org/users/admin/followers"
|
||||
],
|
||||
"atomUri": "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity",
|
||||
"actor": "http://mastodon.example.org/users/admin",
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"sensitive": "as:sensitive",
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"movedTo": "as:movedTo",
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||
"conversation": "ostatus:conversation",
|
||||
"atomUri": "ostatus:atomUri",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"Emoji": "toot:Emoji"
|
||||
}
|
||||
]
|
||||
}
|
63
test/fixtures/mastodon-create-with-attachment.json
vendored
Normal file
63
test/fixtures/mastodon-create-with-attachment.json
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"type": "Create",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"signature": {
|
||||
"type": "RsaSignature2017",
|
||||
"signatureValue": "KnaBoP7C4XYgzTFbM+CpGlx4p59ahWvNNo4reRGDlb/DmxL3OF1/WugNl0xHCOA3aoIX2rrkHniw+z4Yb+wOBf9ZOxgM+IHTKj69AEcm/4NxGXxStRv603JZNyboY371w8g/mIKmLLtL6dgUI3n2Laam2rYh//8aelEWQ240TxiJi/WcKuOT2DNInWOpfArgxJ4MA11n4tb4xX65RkxInTCFa1kaJG8L+A+EoXtIhTa4rCQDv/BH3a8x7vOJxHfEosEnkk/yVEqG+ccgoTvc+5/kK+TKk3S3GuXch0ro9RKqxfPAHkyg8eetRhNhKWZ/rgPNfcF6bGJKFA0i8TzjHw==",
|
||||
"creator": "http://mastodon.example.org/users/admin#main-key",
|
||||
"created": "2018-02-17T17:14:26Z"
|
||||
},
|
||||
"published": "2018-02-17T17:14:26Z",
|
||||
"object": {
|
||||
"url": "http://mastodon.example.org/@admin/99541822081679796",
|
||||
"type": "Note",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"tag": [],
|
||||
"summary": null,
|
||||
"sensitive": false,
|
||||
"published": "2018-02-17T17:14:26Z",
|
||||
"inReplyToAtomUri": null,
|
||||
"inReplyTo": null,
|
||||
"id": "http://mastodon.example.org/users/admin/statuses/99541822081679796",
|
||||
"conversation": "tag:mastodon.example.org,2018-02-17:objectId=10:objectType=Conversation",
|
||||
"content": "<p><a href=\"http://mastodon.example.org/media/hw4nrZmV5DPbW2z_hao\" rel=\"nofollow noopener\" target=\"_blank\"><span class=\"invisible\">http://</span><span class=\"ellipsis\">mastodon.example.org/media/hw4</span><span class=\"invisible\">nrZmV5DPbW2z_hao</span></a></p>",
|
||||
"cc": [
|
||||
"http://mastodon.example.org/users/admin/followers"
|
||||
],
|
||||
"attributedTo": "http://mastodon.example.org/users/admin",
|
||||
"attachment": [
|
||||
{
|
||||
"url": "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
|
||||
"type": "Document",
|
||||
"name": null,
|
||||
"mediaType": "image/jpeg"
|
||||
}
|
||||
],
|
||||
"atomUri": "http://mastodon.example.org/users/admin/statuses/99541822081679796"
|
||||
},
|
||||
"id": "http://mastodon.example.org/users/admin/statuses/99541822081679796/activity",
|
||||
"cc": [
|
||||
"http://mastodon.example.org/users/admin/followers"
|
||||
],
|
||||
"actor": "http://mastodon.example.org/users/admin",
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"sensitive": "as:sensitive",
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"movedTo": "as:movedTo",
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||
"conversation": "ostatus:conversation",
|
||||
"atomUri": "ostatus:atomUri",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"Emoji": "toot:Emoji"
|
||||
}
|
||||
]
|
||||
}
|
33
test/fixtures/mastodon-delete.json
vendored
Normal file
33
test/fixtures/mastodon-delete.json
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"type": "Delete",
|
||||
"signature": {
|
||||
"type": "RsaSignature2017",
|
||||
"signatureValue": "cw0RlfNREf+5VdsOYcCBDrv521eiLsDTAYNHKffjF0bozhCnOh+wHkFik7WamUk$
|
||||
uEiN4L2H6vPlGRprAZGRhEwgy+A7rIFQNmLrpW5qV5UNVI/2F7kngEHqZQgbQYj9hW+5GMYmPkHdv3D72ZefGw$
|
||||
4Xa2NBLGFpAjQllfzt7kzZLKKY2DM99FdUa64I2Wj3iD04Hs23SbrUdAeuGk/c1Cg6bwGNG4vxoiwn1jikgJLA$
|
||||
NAlSGjsRGdR7LfbC7GqWWsW3cSNsLFPoU6FyALjgTrrYoHiXe0QHggw+L3yMLfzB2S/L46/VRbyb+WDKMBIXUL$
|
||||
5owmzHSi6e/ZtCI3w==",
|
||||
"creator": "http://mastodon.example.org/users/gargron#main-key", "created": "2018-03-03T16:24:11Z"
|
||||
},
|
||||
"object": {
|
||||
"type": "Tombstone",
|
||||
"id": "http://mastodon.example.org/users/gargron/statuses/99620895606148759",
|
||||
"atomUri": "http://mastodon.example.org/users/gargron/statuses/99620895606148759"
|
||||
},
|
||||
"id": "http://mastodon.example.org/users/gargron/statuses/99620895606148759#delete",
|
||||
"actor": "http://mastodon.example.org/users/gargron",
|
||||
"@context": [
|
||||
{
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"sensitive": "as:sensitive",
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"movedTo": "as:movedTo",
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||
"conversation": "ostatus:conversation",
|
||||
"atomUri": "ostatus:atomUri",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"Emoji": "toot:Emoji"
|
||||
}
|
||||
]
|
||||
}
|
29
test/fixtures/mastodon-follow-activity.json
vendored
Normal file
29
test/fixtures/mastodon-follow-activity.json
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"type": "Follow",
|
||||
"signature": {
|
||||
"type": "RsaSignature2017",
|
||||
"signatureValue": "Kn1/UkAQGJVaXBfWLAHcnwHg8YMAUqlEaBuYLazAG+pz5hqivsyrBmPV186Xzr+B4ZLExA9+SnOoNx/GOz4hBm0kAmukNSILAsUd84tcJ2yT9zc1RKtembK4WiwOw7li0+maeDN0HaB6t+6eTqsCWmtiZpprhXD8V1GGT8yG7X24fQ9oFGn+ng7lasbcCC0988Y1eGqNe7KryxcPuQz57YkDapvtONzk8gyLTkZMV4De93MyRHq6GVjQVIgtiYabQAxrX6Q8C+4P/jQoqdWJHEe+MY5JKyNaT/hMPt2Md1ok9fZQBGHlErk22/zy8bSN19GdG09HmIysBUHRYpBLig==",
|
||||
"creator": "http://mastodon.example.org/users/admin#main-key",
|
||||
"created": "2018-02-17T13:29:31Z"
|
||||
},
|
||||
"object": "http://localtesting.pleroma.lol/users/lain",
|
||||
"nickname": "lain",
|
||||
"id": "http://mastodon.example.org/users/admin#follows/2",
|
||||
"actor": "http://mastodon.example.org/users/admin",
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"sensitive": "as:sensitive",
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"movedTo": "as:movedTo",
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||
"conversation": "ostatus:conversation",
|
||||
"atomUri": "ostatus:atomUri",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"Emoji": "toot:Emoji"
|
||||
}
|
||||
]
|
||||
}
|
29
test/fixtures/mastodon-like.json
vendored
Normal file
29
test/fixtures/mastodon-like.json
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"type": "Like",
|
||||
"signature": {
|
||||
"type": "RsaSignature2017",
|
||||
"signatureValue": "fdxMfQSMwbC6wP6sh6neS/vM5879K67yQkHTbiT5Npr5wAac0y6+o3Ij+41tN3rL6wfuGTosSBTHOtta6R4GCOOhCaCSLMZKypnp1VltCzLDoyrZELnYQIC8gpUXVmIycZbREk22qWUe/w7DAFaKK4UscBlHDzeDVcA0K3Se5Sluqi9/Zh+ldAnEzj/rSEPDjrtvf5wGNf3fHxbKSRKFt90JvKK6hS+vxKUhlRFDf6/SMETw+EhwJSNW4d10yMUakqUWsFv4Acq5LW7l+HpYMvlYY1FZhNde1+uonnCyuQDyvzkff8zwtEJmAXC4RivO/VVLa17SmqheJZfI8oluVg==",
|
||||
"creator": "http://mastodon.example.org/users/admin#main-key",
|
||||
"created": "2018-02-17T18:57:49Z"
|
||||
},
|
||||
"object": "http://localtesting.pleroma.lol/objects/eb92579d-3417-42a8-8652-2492c2d4f454",
|
||||
"nickname": "lain",
|
||||
"id": "http://mastodon.example.org/users/admin#likes/2",
|
||||
"actor": "http://mastodon.example.org/users/admin",
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"sensitive": "as:sensitive",
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"movedTo": "as:movedTo",
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||
"conversation": "ostatus:conversation",
|
||||
"atomUri": "ostatus:atomUri",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"Emoji": "toot:Emoji"
|
||||
}
|
||||
]
|
||||
}
|
9
test/fixtures/mastodon-note-object.json
vendored
Normal file
9
test/fixtures/mastodon-note-object.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","movedTo":"as:movedTo","Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji"}],"id":"http://mastodon.example.org/users/admin/statuses/99541947525187367","type":"Note","summary":null,"content":"\u003cp\u003eyeah.\u003c/p\u003e","inReplyTo":null,"published":"2018-02-17T17:46:20Z","url":"http://mastodon.example.org/@admin/99541947525187367","attributedTo":"http://mastodon.example.org/users/admin","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["http://mastodon.example.org/users/admin/followers"],"sensitive":false,"atomUri":"http://mastodon.example.org/users/admin/statuses/99541947525187367","inReplyToAtomUri":null,"conversation":"tag:mastodon.example.org,2018-02-17:objectId=59:objectType=Conversation","tag":[],
|
||||
"attachment": [
|
||||
{
|
||||
"url": "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
|
||||
"type": "Document",
|
||||
"name": null,
|
||||
"mediaType": "image/jpeg"
|
||||
}
|
||||
]}
|
38
test/fixtures/mastodon-note-unlisted.xml
vendored
Normal file
38
test/fixtures/mastodon-note-unlisted.xml
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:mastodon="http://mastodon.social/schema/1.0">
|
||||
<id>https://mastodon.social/users/lambadalambda.atom</id>
|
||||
<title>Critical Value</title>
|
||||
<subtitle></subtitle>
|
||||
<updated>2017-04-16T21:47:25Z</updated>
|
||||
<logo>https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif</logo>
|
||||
<author>
|
||||
<id>https://mastodon.social/users/lambadalambda</id>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
|
||||
<uri>https://mastodon.social/users/lambadalambda</uri>
|
||||
<name>lambadalambda</name>
|
||||
<email>lambadalambda@mastodon.social</email>
|
||||
<link rel="alternate" type="text/html" href="https://mastodon.social/@lambadalambda"/>
|
||||
<link rel="avatar" type="image/gif" media:width="120" media:height="120" href="https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif"/>
|
||||
<link rel="header" type="" media:width="700" media:height="335" href="/headers/original/missing.png"/>
|
||||
<poco:preferredUsername>lambadalambda</poco:preferredUsername>
|
||||
<poco:displayName>Critical Value</poco:displayName>
|
||||
<mastodon:scope>public</mastodon:scope>
|
||||
</author>
|
||||
<link rel="alternate" type="text/html" href="https://mastodon.social/@lambadalambda"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/lambadalambda.atom"/>
|
||||
<link rel="hub" href="https://mastodon.social/api/push"/>
|
||||
<link rel="salmon" href="https://mastodon.social/api/salmon/264"/>
|
||||
<entry>
|
||||
<id>tag:mastodon.social,2017-05-10:objectId=5551985:objectType=Status</id>
|
||||
<published>2017-05-10T12:21:36Z</published>
|
||||
<updated>2017-05-10T12:21:36Z</updated>
|
||||
<title>New status by lambadalambda</title>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<summary xml:lang="sv">technologic</summary>
|
||||
<content type="html" xml:lang="sv"><p>test</p></content>
|
||||
<mastodon:scope>unlisted</mastodon:scope>
|
||||
<link rel="alternate" type="text/html" href="https://mastodon.social/users/lambadalambda/updates/2314748"/>
|
||||
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/lambadalambda/updates/2314748.atom"/>
|
||||
</entry>
|
||||
</feed>
|
65
test/fixtures/mastodon-post-activity.json
vendored
Normal file
65
test/fixtures/mastodon-post-activity.json
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"Emoji": "toot:Emoji",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"atomUri": "ostatus:atomUri",
|
||||
"conversation": "ostatus:conversation",
|
||||
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"movedTo": "as:movedTo",
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"sensitive": "as:sensitive",
|
||||
"toot": "http://joinmastodon.org/ns#"
|
||||
}
|
||||
],
|
||||
"actor": "http://mastodon.example.org/users/admin",
|
||||
"cc": [
|
||||
"http://mastodon.example.org/users/admin/followers",
|
||||
"http://localtesting.pleroma.lol/users/lain"
|
||||
],
|
||||
"id": "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity",
|
||||
"nickname": "lain",
|
||||
"object": {
|
||||
"atomUri": "http://mastodon.example.org/users/admin/statuses/99512778738411822",
|
||||
"attachment": [],
|
||||
"attributedTo": "http://mastodon.example.org/users/admin",
|
||||
"cc": [
|
||||
"http://mastodon.example.org/users/admin/followers",
|
||||
"http://localtesting.pleroma.lol/users/lain"
|
||||
],
|
||||
"content": "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>",
|
||||
"conversation": "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation",
|
||||
"id": "http://mastodon.example.org/users/admin/statuses/99512778738411822",
|
||||
"inReplyTo": null,
|
||||
"inReplyToAtomUri": null,
|
||||
"published": "2018-02-12T14:08:20Z",
|
||||
"sensitive": true,
|
||||
"summary": "cw",
|
||||
"tag": [
|
||||
{
|
||||
"href": "http://localtesting.pleroma.lol/users/lain",
|
||||
"name": "@lain@localtesting.pleroma.lol",
|
||||
"type": "Mention"
|
||||
}
|
||||
],
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"type": "Note",
|
||||
"url": "http://mastodon.example.org/@admin/99512778738411822"
|
||||
},
|
||||
"published": "2018-02-12T14:08:20Z",
|
||||
"signature": {
|
||||
"created": "2018-02-12T14:08:20Z",
|
||||
"creator": "http://mastodon.example.org/users/admin#main-key",
|
||||
"signatureValue": "rnNfcopkc6+Ju73P806popcfwrK9wGYHaJVG1/ZvrlEbWVDzaHjkXqj9Q3/xju5l8CSn9tvSgCCtPFqZsFQwn/pFIFUcw7ZWB2xi4bDm3NZ3S4XQ8JRaaX7og5hFxAhWkGhJhAkfxVnOg2hG+w2d/7d7vRVSC1vo5ip4erUaA/PkWusZvPIpxnRWoXaxJsFmVx0gJgjpJkYDyjaXUlp+jmaoseeZ4EPQUWqHLKJ59PRG0mg8j2xAjYH9nQaN14qMRmTGPxY8gfv/CUFcatA+8VJU9KEsJkDAwLVvglydNTLGrxpAJU78a2eaht0foV43XUIZGe3DKiJPgE+UOKGCJw==",
|
||||
"type": "RsaSignature2017"
|
||||
},
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"type": "Create"
|
||||
}
|
43
test/fixtures/mastodon-update.json
vendored
Normal file
43
test/fixtures/mastodon-update.json
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"type": "Update",
|
||||
"object": {
|
||||
"url": "http://mastodon.example.org/@gargron",
|
||||
"type": "Person",
|
||||
"summary": "<p>Some bio</p>",
|
||||
"publicKey": {
|
||||
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0gs3VnQf6am3R+CeBV4H\nlfI1HZTNRIBHgvFszRZkCERbRgEWMu+P+I6/7GJC5H5jhVQ60z4MmXcyHOGmYMK/\n5XyuHQz7V2Ssu1AxLfRN5Biq1ayb0+DT/E7QxNXDJPqSTnstZ6C7zKH/uAETqg3l\nBonjCQWyds+IYbQYxf5Sp3yhvQ80lMwHML3DaNCMlXWLoOnrOX5/yK5+dedesg2\n/HIvGk+HEt36vm6hoH7bwPuEkgA++ACqwjXRe5Mta7i3eilHxFaF8XIrJFARV0t\nqOu4GID/jG6oA+swIWndGrtR2QRJIt9QIBFfK3HG5M0koZbY1eTqwNFRHFL3xaD\nUQIDAQAB\n-----END PUBLIC KEY-----\n",
|
||||
"owner": "http://mastodon.example.org/users/gargron",
|
||||
"id": "http://mastodon.example.org/users/gargron#main-key"
|
||||
},
|
||||
"preferredUsername": "gargron",
|
||||
"outbox": "http://mastodon.example.org/users/gargron/outbox",
|
||||
"name": "gargle",
|
||||
"manuallyApprovesFollowers": false,
|
||||
"inbox": "http://mastodon.example.org/users/gargron/inbox",
|
||||
"id": "http://mastodon.example.org/users/gargron",
|
||||
"following": "http://mastodon.example.org/users/gargron/following",
|
||||
"followers": "http://mastodon.example.org/users/gargron/followers",
|
||||
"endpoints": {
|
||||
"sharedInbox": "http://mastodon.example.org/inbox"
|
||||
},
|
||||
"icon":{"type":"Image","mediaType":"image/jpeg","url":"https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"},"image":{"type":"Image","mediaType":"image/png","url":"https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"}
|
||||
},
|
||||
"id": "http://mastodon.example.org/users/gargron#updates/1519563538",
|
||||
"actor": "http://mastodon.example.org/users/gargron",
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"sensitive": "as:sensitive",
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"movedTo": "as:movedTo",
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||
"conversation": "ostatus:conversation",
|
||||
"atomUri": "ostatus:atomUri",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"Emoji": "toot:Emoji"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -8,9 +8,11 @@ def build(data \\ %{}, opts \\ %{}) do
|
|||
"id" => Pleroma.Web.ActivityPub.Utils.generate_object_id,
|
||||
"actor" => user.ap_id,
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"type" => "Create",
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "test"
|
||||
"content" => "test",
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
}
|
||||
}
|
||||
Map.merge(activity, data)
|
||||
|
@ -23,7 +25,7 @@ def insert(data \\ %{}, opts \\ %{}) do
|
|||
|
||||
def insert_list(times, data \\ %{}, opts \\ %{}) do
|
||||
Enum.map(1..times, fn (n) ->
|
||||
{:ok, activity} = insert(data)
|
||||
{:ok, activity} = insert(data, opts)
|
||||
activity
|
||||
end)
|
||||
end
|
||||
|
@ -32,7 +34,7 @@ def public_and_non_public do
|
|||
user = Pleroma.Factory.insert(:user)
|
||||
|
||||
public = build(%{"id" => 1}, %{user: user})
|
||||
non_public = build(%{"id" => 2, "to" => []}, %{user: user})
|
||||
non_public = build(%{"id" => 2, "to" => [user.follower_address]}, %{user: user})
|
||||
|
||||
{:ok, public} = ActivityPub.insert(public)
|
||||
{:ok, non_public} = ActivityPub.insert(non_public)
|
||||
|
|
|
@ -14,6 +14,8 @@ def build(data \\ %{}) do
|
|||
end
|
||||
|
||||
def insert(data \\ %{}) do
|
||||
Repo.insert(build(data))
|
||||
{:ok, user} = Repo.insert(build(data))
|
||||
User.invalidate_cache(user)
|
||||
{:ok, user}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -52,7 +52,8 @@ def note_activity_factory do
|
|||
|
||||
%Pleroma.Activity{
|
||||
data: data,
|
||||
actor: data["actor"]
|
||||
actor: data["actor"],
|
||||
recipients: data["to"]
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -80,6 +80,13 @@ def get("https://shitposter.club/.well-known/webfinger?resource=https://shitpost
|
|||
}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/5381", [Accept: "application/xrd+xml"], []) do
|
||||
{:ok, %Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/spc_5381_xrd.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://gs.example.org/.well-known/webfinger", [Accept: "application/xrd+xml"], [params: [resource: "http://gs.example.org:4040/index.php/user/1"], follow_redirect: true]) do
|
||||
{:ok, %Response{
|
||||
status_code: 200,
|
||||
|
@ -122,6 +129,13 @@ def get("https://social.heldscal.la/api/statuses/user_timeline/29191.atom", _bod
|
|||
}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/api/statuses/user_timeline/5381.atom", _body, _headers) do
|
||||
{:ok, %Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/spc_5381.atom")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://social.heldscal.la/api/statuses/user_timeline/23211.atom", _body, _headers) do
|
||||
{:ok, %Response{
|
||||
status_code: 200,
|
||||
|
@ -366,6 +380,62 @@ def get("http://social.stopwatchingus-heidelberg.de/.well-known/host-meta", [],
|
|||
}}
|
||||
end
|
||||
|
||||
def get("http://mastodon.example.org/users/admin", ["Accept": "application/activity+json"], _) do
|
||||
{:ok, %Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/admin@mastdon.example.org.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://masto.quad.moe/users/_HellPie", ["Accept": "application/activity+json"], _) do
|
||||
{:ok, %Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/hellpie.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://niu.moe/users/rye", ["Accept": "application/activity+json"], _) do
|
||||
{:ok, %Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/rye.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://mstdn.io/users/mayuutann", ["Accept": "application/activity+json"], _) do
|
||||
{:ok, %Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/mayumayu.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://mastodon.example.org/@admin/99541947525187367", ["Accept": "application/activity+json"], _) do
|
||||
{:ok, %Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/mastodon-note-object.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://mstdn.io/users/mayuutann/statuses/99568293732299394", ["Accept": "application/activity+json"], _) do
|
||||
{:ok, %Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/mayumayupost.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/notice/7369654", _, _) do
|
||||
{:ok, %Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/7369654.html")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/api/statuses/show/7369654.atom", _body, _headers) do
|
||||
{:ok, %Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/7369654.atom")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(url, body, headers) do
|
||||
{:error, "Not implemented the mock response for get #{inspect(url)}, #{inspect(body)}, #{inspect(headers)}"}
|
||||
end
|
||||
|
|
|
@ -46,21 +46,22 @@ test "can't follow a deactivated users" do
|
|||
{:error, _} = User.follow(user, followed)
|
||||
end
|
||||
|
||||
test "following a remote user will ensure a websub subscription is present" do
|
||||
user = insert(:user)
|
||||
{:ok, followed} = OStatus.make_user("shp@social.heldscal.la")
|
||||
# This is a somewhat useless test.
|
||||
# test "following a remote user will ensure a websub subscription is present" do
|
||||
# user = insert(:user)
|
||||
# {:ok, followed} = OStatus.make_user("shp@social.heldscal.la")
|
||||
|
||||
assert followed.local == false
|
||||
# assert followed.local == false
|
||||
|
||||
{:ok, user} = User.follow(user, followed)
|
||||
assert User.ap_followers(followed) in user.following
|
||||
# {:ok, user} = User.follow(user, followed)
|
||||
# assert User.ap_followers(followed) in user.following
|
||||
|
||||
query = from w in WebsubClientSubscription,
|
||||
where: w.topic == ^followed.info["topic"]
|
||||
websub = Repo.one(query)
|
||||
# query = from w in WebsubClientSubscription,
|
||||
# where: w.topic == ^followed.info["topic"]
|
||||
# websub = Repo.one(query)
|
||||
|
||||
assert websub
|
||||
end
|
||||
# assert websub
|
||||
# end
|
||||
|
||||
test "unfollow takes a user and another user" do
|
||||
followed = insert(:user)
|
||||
|
@ -371,4 +372,15 @@ test ".delete deactivates a user, all follow relationships and all create activi
|
|||
|
||||
refute Repo.get(Activity, activity.id)
|
||||
end
|
||||
|
||||
test "get_public_key_for_ap_id fetches a user that's not in the db" do
|
||||
assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin")
|
||||
end
|
||||
|
||||
test "insert or update a user from given data" do
|
||||
user = insert(:user, %{nickname: "nick@name.de"})
|
||||
data = %{ ap_id: user.ap_id <> "xxx", name: user.name, nickname: user.nickname }
|
||||
|
||||
assert {:ok, %User{}} = User.insert_or_update_user(data)
|
||||
end
|
||||
end
|
||||
|
|
49
test/web/activity_pub/activity_pub_controller_test.exs
Normal file
49
test/web/activity_pub/activity_pub_controller_test.exs
Normal file
|
@ -0,0 +1,49 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
import Pleroma.Factory
|
||||
alias Pleroma.Web.ActivityPub.{UserView, ObjectView}
|
||||
alias Pleroma.{Repo, User}
|
||||
alias Pleroma.Activity
|
||||
|
||||
describe "/users/:nickname" do
|
||||
test "it returns a json representation of the user", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
conn = conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get("/users/#{user.nickname}")
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
|
||||
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
|
||||
end
|
||||
end
|
||||
|
||||
describe "/object/:uuid" do
|
||||
test "it returns a json representation of the object", %{conn: conn} do
|
||||
note = insert(:note)
|
||||
uuid = String.split(note.data["id"], "/") |> List.last
|
||||
|
||||
conn = conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get("/objects/#{uuid}")
|
||||
|
||||
assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
|
||||
end
|
||||
end
|
||||
|
||||
describe "/users/:nickname/inbox" do
|
||||
test "it inserts an incoming activity into the database", %{conn: conn} do
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!
|
||||
|
||||
conn = conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/inbox", data)
|
||||
|
||||
assert "ok" == json_response(conn, 200)
|
||||
:timer.sleep(500)
|
||||
assert Activity.get_by_ap_id(data["id"])
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because one or more lines are too long
277
test/web/activity_pub/transmogrifier_test.exs
Normal file
277
test/web/activity_pub/transmogrifier_test.exs
Normal file
|
@ -0,0 +1,277 @@
|
|||
defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.Websub.WebsubClientSubscription
|
||||
alias Pleroma.Web.Websub.WebsubServerSubscription
|
||||
import Ecto.Query
|
||||
|
||||
import Pleroma.Factory
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
describe "handle_incoming" do
|
||||
test "it ignores an incoming notice if we already have it" do
|
||||
activity = insert(:note_activity)
|
||||
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json")
|
||||
|> Poison.decode!
|
||||
|> Map.put("object", activity.data["object"])
|
||||
|
||||
{:ok, returned_activity} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert activity == returned_activity
|
||||
end
|
||||
|
||||
test "it fetches replied-to activities if we don't have them" do
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json")
|
||||
|> Poison.decode!
|
||||
|
||||
object = data["object"]
|
||||
|> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
|
||||
|
||||
data = data
|
||||
|> Map.put("object", object)
|
||||
|
||||
{:ok, returned_activity} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert activity = Activity.get_create_activity_by_object_ap_id("tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment")
|
||||
assert returned_activity.data["object"]["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
|
||||
assert returned_activity.data["object"]["inReplyToStatusId"] == activity.id
|
||||
end
|
||||
|
||||
test "it works for incoming notices" do
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
assert data["id"] == "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity"
|
||||
assert data["context"] == "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
|
||||
assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
assert data["cc"] == [
|
||||
"http://mastodon.example.org/users/admin/followers",
|
||||
"http://localtesting.pleroma.lol/users/lain"
|
||||
]
|
||||
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||
|
||||
object = data["object"]
|
||||
assert object["id"] == "http://mastodon.example.org/users/admin/statuses/99512778738411822"
|
||||
|
||||
assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
assert object["cc"] == [
|
||||
"http://mastodon.example.org/users/admin/followers",
|
||||
"http://localtesting.pleroma.lol/users/lain"
|
||||
]
|
||||
assert object["actor"] == "http://mastodon.example.org/users/admin"
|
||||
assert object["attributedTo"] == "http://mastodon.example.org/users/admin"
|
||||
assert object["context"] == "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
|
||||
assert object["sensitive"] == true
|
||||
end
|
||||
|
||||
test "it works for incoming follow requests" do
|
||||
user = insert(:user)
|
||||
data = File.read!("test/fixtures/mastodon-follow-activity.json") |> Poison.decode!
|
||||
|> Map.put("object", user.ap_id)
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||
assert data["type"] == "Follow"
|
||||
assert data["id"] == "http://mastodon.example.org/users/admin#follows/2"
|
||||
assert User.following?(User.get_by_ap_id(data["actor"]), user)
|
||||
end
|
||||
|
||||
test "it works for incoming likes" do
|
||||
user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
||||
|
||||
data = File.read!("test/fixtures/mastodon-like.json") |> Poison.decode!
|
||||
|> Map.put("object", activity.data["object"]["id"])
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||
assert data["type"] == "Like"
|
||||
assert data["id"] == "http://mastodon.example.org/users/admin#likes/2"
|
||||
assert data["object"] == activity.data["object"]["id"]
|
||||
end
|
||||
|
||||
test "it works for incoming announces" do
|
||||
data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||
assert data["type"] == "Announce"
|
||||
assert data["id"] == "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
|
||||
assert data["object"] == "http://mastodon.example.org/users/admin/statuses/99541947525187367"
|
||||
|
||||
assert Activity.get_create_activity_by_object_ap_id(data["object"])
|
||||
end
|
||||
|
||||
test "it works for incoming announces with an existing activity" do
|
||||
user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
|
||||
|
||||
data = File.read!("test/fixtures/mastodon-announce.json")
|
||||
|> Poison.decode!
|
||||
|> Map.put("object", activity.data["object"]["id"])
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||
assert data["type"] == "Announce"
|
||||
assert data["id"] == "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
|
||||
assert data["object"] == activity.data["object"]["id"]
|
||||
|
||||
assert Activity.get_create_activity_by_object_ap_id(data["object"]).id == activity.id
|
||||
end
|
||||
|
||||
test "it works for incoming update activities" do
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!
|
||||
object = update_data["object"]
|
||||
|> Map.put("actor", data["actor"])
|
||||
|> Map.put("id", data["actor"])
|
||||
|
||||
update_data = update_data
|
||||
|> Map.put("actor", data["actor"])
|
||||
|> Map.put("object", object)
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
|
||||
|
||||
user = User.get_cached_by_ap_id(data["actor"])
|
||||
assert user.name == "gargle"
|
||||
assert user.avatar["url"] == [%{"href" => "https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"}]
|
||||
assert user.info["banner"]["url"] == [%{"href" => "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"}]
|
||||
assert user.bio == "<p>Some bio</p>"
|
||||
end
|
||||
|
||||
test "it works for incoming deletes" do
|
||||
activity = insert(:note_activity)
|
||||
data = File.read!("test/fixtures/mastodon-delete.json")
|
||||
|> Poison.decode!
|
||||
|
||||
object = data["object"]
|
||||
|> Map.put("id", activity.data["object"]["id"])
|
||||
|
||||
data = data
|
||||
|> Map.put("object", object)
|
||||
|> Map.put("actor", activity.data["actor"])
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
refute Repo.get(Activity, activity.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe "prepare outgoing" do
|
||||
test "it turns mentions into tags" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"})
|
||||
|
||||
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
object = modified["object"]
|
||||
|
||||
expected_mention = %{
|
||||
"href" => other_user.ap_id,
|
||||
"name" => "@#{other_user.nickname}",
|
||||
"type" => "Mention"
|
||||
}
|
||||
|
||||
expected_tag = %{
|
||||
"href" => Pleroma.Web.Endpoint.url <> "/tags/2hu",
|
||||
"type" => "Hashtag",
|
||||
"name" => "#2hu"
|
||||
}
|
||||
|
||||
assert Enum.member?(object["tag"], expected_tag)
|
||||
assert Enum.member?(object["tag"], expected_mention)
|
||||
end
|
||||
|
||||
test "it adds the sensitive property" do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"})
|
||||
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
|
||||
assert modified["object"]["sensitive"]
|
||||
end
|
||||
|
||||
test "it adds the json-ld context and the conversation property" do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
|
||||
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
|
||||
assert modified["@context"] == "https://www.w3.org/ns/activitystreams"
|
||||
assert modified["object"]["conversation"] == modified["context"]
|
||||
end
|
||||
|
||||
test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
|
||||
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
|
||||
assert modified["object"]["actor"] == modified["object"]["attributedTo"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "user upgrade" do
|
||||
test "it upgrades a user to activitypub" do
|
||||
user = insert(:user, %{nickname: "rye@niu.moe", local: false, ap_id: "https://niu.moe/users/rye", follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})})
|
||||
user_two = insert(:user, %{following: [user.follower_address]})
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
|
||||
{:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
|
||||
assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
assert user.info["note_count"] == 1
|
||||
|
||||
{:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
|
||||
assert user.info["ap_enabled"]
|
||||
assert user.info["note_count"] == 1
|
||||
assert user.follower_address == "https://niu.moe/users/rye/followers"
|
||||
|
||||
# Wait for the background task
|
||||
:timer.sleep(1000)
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
assert user.info["note_count"] == 1
|
||||
|
||||
activity = Repo.get(Activity, activity.id)
|
||||
assert user.follower_address in activity.recipients
|
||||
assert %{"url" => [%{"href" => "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"}]} = user.avatar
|
||||
assert %{"url" => [%{"href" => "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"}]} = user.info["banner"]
|
||||
refute "..." in activity.recipients
|
||||
|
||||
unrelated_activity = Repo.get(Activity, unrelated_activity.id)
|
||||
refute user.follower_address in unrelated_activity.recipients
|
||||
|
||||
user_two = Repo.get(User, user_two.id)
|
||||
assert user.follower_address in user_two.following
|
||||
refute "..." in user_two.following
|
||||
end
|
||||
end
|
||||
|
||||
describe "maybe_retire_websub" do
|
||||
test "it deletes all websub client subscripitions with the user as topic" do
|
||||
subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/rye.atom"}
|
||||
{:ok, ws} = Repo.insert(subscription)
|
||||
|
||||
subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/pasty.atom"}
|
||||
{:ok, ws2} = Repo.insert(subscription)
|
||||
|
||||
Transmogrifier.maybe_retire_websub("https://niu.moe/users/rye")
|
||||
|
||||
refute Repo.get(WebsubClientSubscription, ws.id)
|
||||
assert Repo.get(WebsubClientSubscription, ws2.id)
|
||||
end
|
||||
end
|
||||
end
|
17
test/web/activity_pub/views/object_view_test.exs
Normal file
17
test/web/activity_pub/views/object_view_test.exs
Normal file
|
@ -0,0 +1,17 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectViewTest do
|
||||
use Pleroma.DataCase
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.Web.ActivityPub.ObjectView
|
||||
|
||||
test "renders a note object" do
|
||||
note = insert(:note)
|
||||
|
||||
result = ObjectView.render("object.json", %{object: note})
|
||||
|
||||
assert result["id"] == note.data["id"]
|
||||
assert result["to"] == note.data["to"]
|
||||
assert result["content"] == note.data["content"]
|
||||
assert result["type"] == "Note"
|
||||
end
|
||||
end
|
18
test/web/activity_pub/views/user_view_test.exs
Normal file
18
test/web/activity_pub/views/user_view_test.exs
Normal file
|
@ -0,0 +1,18 @@
|
|||
defmodule Pleroma.Web.ActivityPub.UserViewTest do
|
||||
use Pleroma.DataCase
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.Web.ActivityPub.UserView
|
||||
|
||||
test "Renders a user, including the public key" do
|
||||
user = insert(:user)
|
||||
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
|
||||
|
||||
result = UserView.render("user.json", %{user: user})
|
||||
|
||||
assert result["id"] == user.ap_id
|
||||
assert result["preferredUsername"] == user.nickname
|
||||
|
||||
assert String.contains?(result["publicKey"]["publicKeyPem"], "BEGIN RSA PUBLIC KEY")
|
||||
end
|
||||
end
|
154
test/web/http_sigs/http_sig_test.exs
Normal file
154
test/web/http_sigs/http_sig_test.exs
Normal file
|
@ -0,0 +1,154 @@
|
|||
# http signatures
|
||||
# Test data from https://tools.ietf.org/html/draft-cavage-http-signatures-08#appendix-C
|
||||
defmodule Pleroma.Web.HTTPSignaturesTest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.Web.HTTPSignatures
|
||||
import Pleroma.Factory
|
||||
|
||||
@private_key (hd(:public_key.pem_decode(File.read!("test/web/http_sigs/priv.key")))
|
||||
|> :public_key.pem_entry_decode())
|
||||
|
||||
@public_key (hd(:public_key.pem_decode(File.read!("test/web/http_sigs/pub.key")))
|
||||
|> :public_key.pem_entry_decode())
|
||||
|
||||
@headers %{
|
||||
"(request-target)" => "post /foo?param=value&pet=dog",
|
||||
"host" => "example.com",
|
||||
"date" => "Thu, 05 Jan 2014 21:31:40 GMT",
|
||||
"content-type" => "application/json",
|
||||
"digest" => "SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=",
|
||||
"content-length" => "18"
|
||||
}
|
||||
|
||||
@body "{\"hello\": \"world\"}"
|
||||
|
||||
@default_signature """
|
||||
keyId="Test",algorithm="rsa-sha256",signature="jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9HpFQlG7N4YcJPteKTu4MWCLyk+gIr0wDgqtLWf9NLpMAMimdfsH7FSWGfbMFSrsVTHNTk0rK3usrfFnti1dxsM4jl0kYJCKTGI/UWkqiaxwNiKqGcdlEDrTcUhhsFsOIo8VhddmZTZ8w="
|
||||
"""
|
||||
|
||||
@basic_signature """
|
||||
keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date",signature="HUxc9BS3P/kPhSmJo+0pQ4IsCo007vkv6bUm4Qehrx+B1Eo4Mq5/6KylET72ZpMUS80XvjlOPjKzxfeTQj4DiKbAzwJAb4HX3qX6obQTa00/qPDXlMepD2JtTw33yNnm/0xV7fQuvILN/ys+378Ysi082+4xBQFwvhNvSoVsGv4="
|
||||
"""
|
||||
|
||||
@all_headers_signature """
|
||||
keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date content-type digest content-length",signature="Ef7MlxLXoBovhil3AlyjtBwAL9g4TN3tibLj7uuNB3CROat/9KaeQ4hW2NiJ+pZ6HQEOx9vYZAyi+7cmIkmJszJCut5kQLAwuX+Ms/mUFvpKlSo9StS2bMXDBNjOh4Auj774GFj4gwjS+3NhFeoqyr/MuN6HsEnkvn6zdgfE2i0="
|
||||
"""
|
||||
|
||||
test "split up a signature" do
|
||||
expected = %{
|
||||
"keyId" => "Test",
|
||||
"algorithm" => "rsa-sha256",
|
||||
"signature" => "jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9HpFQlG7N4YcJPteKTu4MWCLyk+gIr0wDgqtLWf9NLpMAMimdfsH7FSWGfbMFSrsVTHNTk0rK3usrfFnti1dxsM4jl0kYJCKTGI/UWkqiaxwNiKqGcdlEDrTcUhhsFsOIo8VhddmZTZ8w=",
|
||||
"headers" => ["date"]
|
||||
}
|
||||
|
||||
assert HTTPSignatures.split_signature(@default_signature) == expected
|
||||
end
|
||||
|
||||
test "validates the default case" do
|
||||
signature = HTTPSignatures.split_signature(@default_signature)
|
||||
assert HTTPSignatures.validate(@headers, signature, @public_key)
|
||||
end
|
||||
|
||||
test "validates the basic case" do
|
||||
signature = HTTPSignatures.split_signature(@basic_signature)
|
||||
assert HTTPSignatures.validate(@headers, signature, @public_key)
|
||||
end
|
||||
|
||||
test "validates the all-headers case" do
|
||||
signature = HTTPSignatures.split_signature(@all_headers_signature)
|
||||
assert HTTPSignatures.validate(@headers, signature, @public_key)
|
||||
end
|
||||
|
||||
test "it contructs a signing string" do
|
||||
expected = "date: Thu, 05 Jan 2014 21:31:40 GMT\ncontent-length: 18"
|
||||
assert expected == HTTPSignatures.build_signing_string(@headers, ["date", "content-length"])
|
||||
end
|
||||
|
||||
test "it validates a conn" do
|
||||
public_key_pem = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnGb42rPZIapY4Hfhxrgn\nxKVJczBkfDviCrrYaYjfGxawSw93dWTUlenCVTymJo8meBlFgIQ70ar4rUbzl6GX\nMYvRdku072d1WpglNHXkjKPkXQgngFDrh2sGKtNB/cEtJcAPRO8OiCgPFqRtMiNM\nc8VdPfPdZuHEIZsJ/aUM38EnqHi9YnVDQik2xxDe3wPghOhqjxUM6eLC9jrjI+7i\naIaEygUdyst9qVg8e2FGQlwAeS2Eh8ygCxn+bBlT5OyV59jSzbYfbhtF2qnWHtZy\nkL7KOOwhIfGs7O9SoR2ZVpTEQ4HthNzainIe/6iCR5HGrao/T8dygweXFYRv+k5A\nPQIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||
[public_key] = :public_key.pem_decode(public_key_pem)
|
||||
|
||||
public_key = public_key
|
||||
|> :public_key.pem_entry_decode()
|
||||
|
||||
conn = %{
|
||||
req_headers: [
|
||||
{"host", "localtesting.pleroma.lol"},
|
||||
{"connection", "close"},
|
||||
{"content-length", "2316"},
|
||||
{"user-agent", "http.rb/2.2.2 (Mastodon/2.1.0.rc3; +http://mastodon.example.org/)"},
|
||||
{"date", "Sun, 10 Dec 2017 14:23:49 GMT"},
|
||||
{"digest", "SHA-256=x/bHADMW8qRrq2NdPb5P9fl0lYpKXXpe5h5maCIL0nM="},
|
||||
{"content-type", "application/activity+json"},
|
||||
{"(request-target)", "post /users/demiurge/inbox"},
|
||||
{"signature", "keyId=\"http://mastodon.example.org/users/admin#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"i0FQvr51sj9BoWAKydySUAO1RDxZmNY6g7M62IA7VesbRSdFZZj9/fZapLp6YSuvxUF0h80ZcBEq9GzUDY3Chi9lx6yjpUAS2eKb+Am/hY3aswhnAfYd6FmIdEHzsMrpdKIRqO+rpQ2tR05LwiGEHJPGS0p528NvyVxrxMT5H5yZS5RnxY5X2HmTKEgKYYcvujdv7JWvsfH88xeRS7Jlq5aDZkmXvqoR4wFyfgnwJMPLel8P/BUbn8BcXglH/cunR0LUP7sflTxEz+Rv5qg+9yB8zgBsB4C0233WpcJxjeD6Dkq0EcoJObBR56F8dcb7NQtUDu7x6xxzcgSd7dHm5w==\""}]
|
||||
}
|
||||
|
||||
assert HTTPSignatures.validate_conn(conn, public_key)
|
||||
end
|
||||
|
||||
test "it validates a conn and fetches the key" do
|
||||
conn = %{
|
||||
params: %{"actor" => "http://mastodon.example.org/users/admin"},
|
||||
req_headers: [
|
||||
{"host", "localtesting.pleroma.lol"},
|
||||
{"x-forwarded-for", "127.0.0.1"},
|
||||
{"connection", "close"},
|
||||
{"content-length", "2307"},
|
||||
{"user-agent", "http.rb/2.2.2 (Mastodon/2.1.0.rc3; +http://mastodon.example.org/)"},
|
||||
{"date", "Sun, 11 Feb 2018 17:12:01 GMT"},
|
||||
{"digest", "SHA-256=UXsAnMtR9c7mi1FOf6HRMtPgGI1yi2e9nqB/j4rZ99I="},
|
||||
{"content-type", "application/activity+json"},
|
||||
{"signature", "keyId=\"http://mastodon.example.org/users/admin#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"qXKqpQXUpC3d9bZi2ioEeAqP8nRMD021CzH1h6/w+LRk4Hj31ARJHDwQM+QwHltwaLDUepshMfz2WHSXAoLmzWtvv7xRwY+mRqe+NGk1GhxVZ/LSrO/Vp7rYfDpfdVtkn36LU7/Bzwxvvaa4ZWYltbFsRBL0oUrqsfmJFswNCQIG01BB52BAhGSCORHKtQyzo1IZHdxl8y80pzp/+FOK2SmHkqWkP9QbaU1qTZzckL01+7M5btMW48xs9zurEqC2sM5gdWMQSZyL6isTV5tmkTZrY8gUFPBJQZgihK44v3qgfWojYaOwM8ATpiv7NG8wKN/IX7clDLRMA8xqKRCOKw==\""},
|
||||
{"(request-target)", "post /users/demiurge/inbox"}
|
||||
]
|
||||
}
|
||||
|
||||
assert HTTPSignatures.validate_conn(conn)
|
||||
end
|
||||
|
||||
test "validate this" do
|
||||
conn = %{
|
||||
params: %{"actor" => "https://niu.moe/users/rye"},
|
||||
req_headers: [
|
||||
{"x-forwarded-for", "149.202.73.191"},
|
||||
{"host", "testing.pleroma.lol"},
|
||||
{"x-cluster-client-ip", "149.202.73.191"},
|
||||
{"connection", "upgrade"},
|
||||
{"content-length", "2396"},
|
||||
{"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://niu.moe/)"},
|
||||
{"date", "Sun, 18 Feb 2018 20:31:51 GMT"},
|
||||
{"digest", "SHA-256=dzH+vLyhxxALoe9RJdMl4hbEV9bGAZnSfddHQzeidTU="},
|
||||
{"content-type", "application/activity+json"},
|
||||
{"signature", "keyId=\"https://niu.moe/users/rye#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"wtxDg4kIpW7nsnUcVJhBk6SgJeDZOocr8yjsnpDRqE52lR47SH6X7G16r7L1AUJdlnbfx7oqcvomoIJoHB3ghP6kRnZW6MyTMZ2jPoi3g0iC5RDqv6oAmDSO14iw6U+cqZbb3P/odS5LkbThF0UNXcfenVNfsKosIJycFjhNQc54IPCDXYq/7SArEKJp8XwEgzmiC2MdxlkVIUSTQYfjM4EG533cwlZocw1mw72e5mm/owTa80BUZAr0OOuhoWARJV9btMb02ZyAF6SCSoGPTA37wHyfM1Dk88NHf7Z0Aov/Fl65dpRM+XyoxdkpkrhDfH9qAx4iuV2VEWddQDiXHA==\""},
|
||||
{"(request-target)", "post /inbox"}
|
||||
]
|
||||
}
|
||||
assert HTTPSignatures.validate_conn(conn)
|
||||
end
|
||||
|
||||
test "validate this too" do
|
||||
conn = %{
|
||||
params: %{"actor" => "https://niu.moe/users/rye"},
|
||||
req_headers: [
|
||||
{"x-forwarded-for", "149.202.73.191"},
|
||||
{"host", "testing.pleroma.lol"},
|
||||
{"x-cluster-client-ip", "149.202.73.191"},
|
||||
{"connection", "upgrade"},
|
||||
{"content-length", "2342"},
|
||||
{"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://niu.moe/)"},
|
||||
{"date", "Sun, 18 Feb 2018 21:44:46 GMT"},
|
||||
{"digest", "SHA-256=vS8uDOJlyAu78cF3k5EzrvaU9iilHCX3chP37gs5sS8="},
|
||||
{"content-type", "application/activity+json"},
|
||||
{"signature", "keyId=\"https://niu.moe/users/rye#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"IN6fHD8pLiDEf35dOaRHzJKc1wBYh3/Yq0ItaNGxUSbJTd2xMjigZbcsVKzvgYYjglDDN+disGNeD+OBKwMqkXWaWe/lyMc9wHvCH5NMhpn/A7qGLY8yToSt4vh8ytSkZKO6B97yC+Nvy6Fz/yMbvKtFycIvSXCq417cMmY6f/aG+rtMUlTbKO5gXzC7SUgGJCtBPCh1xZzu5/w0pdqdjO46ePNeR6JyJSLLV4hfo3+p2n7SRraxM4ePVCUZqhwS9LPt3Zdhy3ut+IXCZgMVIZggQFM+zXLtcXY5HgFCsFQr5WQDu+YkhWciNWtKFnWfAsnsg5sC330lZ/0Z8Z91yA==\""},
|
||||
{"(request-target)", "post /inbox"}
|
||||
]}
|
||||
assert HTTPSignatures.validate_conn(conn)
|
||||
end
|
||||
|
||||
test "it generates a signature" do
|
||||
user = insert(:user)
|
||||
assert HTTPSignatures.sign(user, %{host: "mastodon.example.org"}) =~ "keyId=\""
|
||||
end
|
||||
end
|
15
test/web/http_sigs/priv.key
Normal file
15
test/web/http_sigs/priv.key
Normal file
|
@ -0,0 +1,15 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF
|
||||
NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F
|
||||
UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB
|
||||
AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA
|
||||
QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK
|
||||
kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg
|
||||
f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u
|
||||
412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc
|
||||
mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7
|
||||
kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA
|
||||
gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW
|
||||
G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
|
||||
7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA==
|
||||
-----END RSA PRIVATE KEY-----
|
6
test/web/http_sigs/pub.key
Normal file
6
test/web/http_sigs/pub.key
Normal file
|
@ -0,0 +1,6 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3
|
||||
6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6
|
||||
Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw
|
||||
oYi+1hqp1fIekaxsyQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
|
@ -121,6 +121,7 @@ test "an announce activity" do
|
|||
#{note_xml}
|
||||
</activity:object>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{note.data["actor"]}"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
"""
|
||||
|
||||
announce_xml = ActivityRepresenter.to_simple_form(announce, user)
|
||||
|
@ -156,6 +157,7 @@ test "a like activity" do
|
|||
<link rel="self" type="application/atom+xml" href="#{like.data["id"]}"/>
|
||||
<thr:in-reply-to ref="#{note.data["id"]}" />
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{note.data["actor"]}"/>
|
||||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||
"""
|
||||
|
||||
assert clean(res) == clean(expected)
|
||||
|
|
|
@ -43,7 +43,7 @@ test "gets a feed", %{conn: conn} do
|
|||
conn = conn
|
||||
|> get("/users/#{user.nickname}/feed.atom")
|
||||
|
||||
assert response(conn, 200)
|
||||
assert response(conn, 200) =~ note_activity.data["object"]["content"]
|
||||
end
|
||||
|
||||
test "gets an object", %{conn: conn} do
|
||||
|
|
|
@ -90,6 +90,15 @@ test "handle incoming notes - Mastodon, with CW" do
|
|||
assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
|
||||
end
|
||||
|
||||
test "handle incoming unlisted messages, put public into cc" do
|
||||
incoming = File.read!("test/fixtures/mastodon-note-unlisted.xml")
|
||||
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
||||
refute "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
|
||||
assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["cc"]
|
||||
refute "https://www.w3.org/ns/activitystreams#Public" in activity.data["object"]["to"]
|
||||
assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["object"]["cc"]
|
||||
end
|
||||
|
||||
test "handle incoming retweets - Mastodon, with CW" do
|
||||
incoming = File.read!("test/fixtures/cw_retweet.xml")
|
||||
{:ok, [[_activity, retweeted_activity]]} = OStatus.handle_incoming(incoming)
|
||||
|
@ -306,7 +315,8 @@ test "it returns user info in a hash" do
|
|||
"fqn" => user,
|
||||
"bio" => "cofe",
|
||||
"avatar" => %{"type" => "Image", "url" => [%{"href" => "https://social.heldscal.la/avatar/29191-original-20170421154949.jpeg", "mediaType" => "image/jpeg", "type" => "Link"}]},
|
||||
"subscribe_address" => "https://social.heldscal.la/main/ostatussub?profile={uri}"
|
||||
"subscribe_address" => "https://social.heldscal.la/main/ostatussub?profile={uri}",
|
||||
"ap_id" => nil
|
||||
}
|
||||
assert data == expected
|
||||
end
|
||||
|
@ -330,7 +340,8 @@ test "it works with the uri" do
|
|||
"fqn" => user,
|
||||
"bio" => "cofe",
|
||||
"avatar" => %{"type" => "Image", "url" => [%{"href" => "https://social.heldscal.la/avatar/29191-original-20170421154949.jpeg", "mediaType" => "image/jpeg", "type" => "Link"}]},
|
||||
"subscribe_address" => "https://social.heldscal.la/main/ostatussub?profile={uri}"
|
||||
"subscribe_address" => "https://social.heldscal.la/main/ostatussub?profile={uri}",
|
||||
"ap_id" => nil
|
||||
}
|
||||
assert data == expected
|
||||
end
|
||||
|
@ -355,13 +366,6 @@ test "it works for atom notes, too" do
|
|||
end
|
||||
end
|
||||
|
||||
test "insert or update a user from given data" do
|
||||
user = insert(:user, %{nickname: "nick@name.de"})
|
||||
data = %{ ap_id: user.ap_id <> "xxx", name: user.name, nickname: user.nickname }
|
||||
|
||||
assert {:ok, %User{}} = OStatus.insert_or_update_user(data)
|
||||
end
|
||||
|
||||
test "it doesn't add nil in the do field" do
|
||||
incoming = File.read!("test/fixtures/nil_mention_entry.xml")
|
||||
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
||||
|
|
|
@ -22,6 +22,7 @@ test "returns a user with id, uri, name and link" do
|
|||
<name>#{user.nickname}</name>
|
||||
<link rel="avatar" href="#{User.avatar_url(user)}" />
|
||||
<link rel="header" href="#{User.banner_url(user)}" />
|
||||
<ap_enabled>true</ap_enabled>
|
||||
"""
|
||||
|
||||
assert clean(res) == clean(expected)
|
||||
|
|
|
@ -59,7 +59,6 @@ test "encodes an xml payload with a private key" do
|
|||
end
|
||||
|
||||
test "it gets a magic key" do
|
||||
# TODO: Make test local
|
||||
salmon = File.read!("test/fixtures/salmon2.xml")
|
||||
{:ok, key} = Salmon.fetch_magic_key(salmon)
|
||||
|
||||
|
@ -86,7 +85,7 @@ test "it pushes an activity to remote accounts it's addressed to" do
|
|||
"context" => note.data["context"]
|
||||
}
|
||||
|
||||
{:ok, activity} = Repo.insert(%Activity{data: activity_data})
|
||||
{:ok, activity} = Repo.insert(%Activity{data: activity_data, recipients: activity_data["to"]})
|
||||
user = Repo.get_by(User, ap_id: activity.data["actor"])
|
||||
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
|
||||
|
||||
|
|
|
@ -75,17 +75,17 @@ test "an activity" do
|
|||
date = DateTime.from_naive!(~N[2016-05-24 13:26:08.003], "Etc/UTC") |> DateTime.to_iso8601
|
||||
|
||||
{:ok, convo_object} = Object.context_mapping("2hu") |> Repo.insert
|
||||
|
||||
to = [
|
||||
User.ap_followers(user),
|
||||
"https://www.w3.org/ns/activitystreams#Public",
|
||||
mentioned_user.ap_id
|
||||
]
|
||||
activity = %Activity{
|
||||
id: 1,
|
||||
data: %{
|
||||
"type" => "Create",
|
||||
"id" => "id",
|
||||
"to" => [
|
||||
User.ap_followers(user),
|
||||
"https://www.w3.org/ns/activitystreams#Public",
|
||||
mentioned_user.ap_id
|
||||
],
|
||||
"to" => to,
|
||||
"actor" => User.ap_id(user),
|
||||
"object" => %{
|
||||
"published" => date,
|
||||
|
@ -108,7 +108,8 @@ test "an activity" do
|
|||
"published" => date,
|
||||
"context" => "2hu"
|
||||
},
|
||||
local: false
|
||||
local: false,
|
||||
recipients: to
|
||||
}
|
||||
|
||||
expected_html = "<span>2hu</span><br />alert('YAY')Some <img height='32px' width='32px' alt='2hu' title='2hu' src='corndog.png' /> content mentioning <a href=\"#{mentioned_user.ap_id}\">@shp</a>"
|
||||
|
@ -134,7 +135,7 @@ test "an activity" do
|
|||
"favorited" => false,
|
||||
"repeated" => false,
|
||||
"external_url" => "some url",
|
||||
"tags" => ["content", "mentioning", "nsfw"],
|
||||
"tags" => ["nsfw", "content", "mentioning"],
|
||||
"activity_type" => "post",
|
||||
"possibly_sensitive" => true,
|
||||
"uri" => activity.data["object"]["id"]
|
||||
|
|
|
@ -28,4 +28,24 @@ test "represent an image attachment" do
|
|||
|
||||
assert expected_object == ObjectRepresenter.to_map(object)
|
||||
end
|
||||
|
||||
test "represents mastodon-style attachments" do
|
||||
object = %Object{
|
||||
id: nil,
|
||||
data: %{
|
||||
"mediaType" => "image/png",
|
||||
"name" => "blabla", "type" => "Document",
|
||||
"url" => "http://mastodon.example.org/system/media_attachments/files/000/000/001/original/8619f31c6edec470.png"
|
||||
}
|
||||
}
|
||||
|
||||
expected_object = %{
|
||||
url: "http://mastodon.example.org/system/media_attachments/files/000/000/001/original/8619f31c6edec470.png",
|
||||
mimetype: "image/png",
|
||||
oembed: false,
|
||||
id: nil
|
||||
}
|
||||
|
||||
assert expected_object == ObjectRepresenter.to_map(object)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -376,9 +376,10 @@ test "without valid credentials", %{conn: conn} do
|
|||
end
|
||||
|
||||
test "with credentials", %{conn: conn, user: current_user} do
|
||||
avatar_image = File.read!("test/fixtures/avatar_data_uri")
|
||||
conn = conn
|
||||
|> with_credentials(current_user.nickname, "test")
|
||||
|> post("/api/qvitter/update_avatar.json", %{img: Pleroma.Web.ActivityPub.ActivityPubTest.data_uri})
|
||||
|> post("/api/qvitter/update_avatar.json", %{img: avatar_image})
|
||||
|
||||
current_user = Repo.get(User, current_user.id)
|
||||
assert is_map(current_user.avatar)
|
||||
|
|
|
@ -38,9 +38,9 @@ test "create a status" do
|
|||
assert get_in(activity.data, ["object", "type"]) == "Note"
|
||||
assert get_in(activity.data, ["object", "actor"]) == user.ap_id
|
||||
assert get_in(activity.data, ["actor"]) == user.ap_id
|
||||
assert Enum.member?(get_in(activity.data, ["to"]), User.ap_followers(user))
|
||||
assert Enum.member?(get_in(activity.data, ["cc"]), User.ap_followers(user))
|
||||
assert Enum.member?(get_in(activity.data, ["to"]), "https://www.w3.org/ns/activitystreams#Public")
|
||||
assert Enum.member?(get_in(activity.data, ["to"]), "shp")
|
||||
assert Enum.member?(get_in(activity.data, ["cc"]), "shp")
|
||||
assert activity.local == true
|
||||
|
||||
assert %{"moominmamma" => "http://localhost:4001/finmoji/128px/moominmamma-128.png"} = activity.data["object"]["emoji"]
|
||||
|
@ -80,7 +80,6 @@ test "create a status that is a reply" do
|
|||
assert get_in(reply.data, ["object", "context"]) == get_in(activity.data, ["object", "context"])
|
||||
assert get_in(reply.data, ["object", "inReplyTo"]) == get_in(activity.data, ["object", "id"])
|
||||
assert get_in(reply.data, ["object", "inReplyToStatusId"]) == activity.id
|
||||
assert Enum.member?(get_in(reply.data, ["to"]), user.ap_id)
|
||||
end
|
||||
|
||||
test "fetch public statuses, excluding remote ones." do
|
||||
|
@ -99,7 +98,7 @@ test "fetch whole known network statuses" do
|
|||
%{ public: activity, user: user } = ActivityBuilder.public_and_non_public
|
||||
insert(:note_activity, %{local: false})
|
||||
|
||||
follower = insert(:user, following: [User.ap_followers(user)])
|
||||
follower = insert(:user, following: [user.follower_address])
|
||||
|
||||
statuses = TwitterAPI.fetch_public_and_external_statuses(follower)
|
||||
|
||||
|
|
Loading…
Reference in a new issue