Merge branch 'develop' into feature/activitypub

This commit is contained in:
Roger Braun 2018-02-11 09:50:55 +01:00
commit 52200998c9
101 changed files with 661 additions and 146 deletions

View file

@ -22,7 +22,7 @@ No release has been made yet, but several servers have been online for months al
### Dependencies
* Postgresql version 9.6 or newer
* Elixir version 1.4 or newer (you will also need erlang-dev, erlang-parsetools, erlang-xmerl packages)
* Elixir version 1.5 or newer
* Build-essential tools
### Configuration
@ -50,3 +50,12 @@ Logs can be watched by using `journalctl -fu pleroma.service`
### Standalone/run by other means
Run `mix phx.server` in repository's root, it will output log into stdout/stderr
### Using an upstream proxy for federation
Add the following to your `dev.secret.exs` or `prod.secret.exs` if you want to proxify all http requests that pleroma makes to an upstream proxy server:
config :pleroma, :http,
proxy_url: "127.0.0.1:8123"
This is useful for running pleroma inside Tor or i2p.

View file

@ -33,7 +33,7 @@
config :pleroma, :websub, Pleroma.Web.Websub
config :pleroma, :ostatus, Pleroma.Web.OStatus
config :pleroma, :httpoison, HTTPoison
config :pleroma, :httpoison, Pleroma.HTTP
version = with {version, 0} <- System.cmd("git", ["rev-parse", "HEAD"]) do
"Pleroma #{String.trim(version)}"
@ -41,6 +41,10 @@
_ -> "Pleroma dev"
end
# Configures http settings, upstream proxy etc.
config :pleroma, :http,
proxy_url: nil
config :pleroma, :instance,
version: version,
name: "Pleroma",
@ -48,6 +52,14 @@
limit: 5000,
registrations_open: true
config :pleroma, :media_proxy,
enabled: false,
redirect_on_failure: true
#base_url: "https://cache.pleroma.social"
config :pleroma, :chat,
enabled: true
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env}.exs"

View file

@ -1 +1,31 @@
firefox, /emoji/Firefox.gif
blank, /emoji/blank.png
f_00b, /emoji/f_00b.png
f_00b11b, /emoji/f_00b11b.png
f_00b33b, /emoji/f_00b33b.png
f_00h, /emoji/f_00h.png
f_00t, /emoji/f_00t.png
f_01b, /emoji/f_01b.png
f_03b, /emoji/f_03b.png
f_10b, /emoji/f_10b.png
f_11b, /emoji/f_11b.png
f_11b00b, /emoji/f_11b00b.png
f_11b22b, /emoji/f_11b22b.png
f_11h, /emoji/f_11h.png
f_11t, /emoji/f_11t.png
f_12b, /emoji/f_12b.png
f_21b, /emoji/f_21b.png
f_22b, /emoji/f_22b.png
f_22b11b, /emoji/f_22b11b.png
f_22b33b, /emoji/f_22b33b.png
f_22h, /emoji/f_22h.png
f_22t, /emoji/f_22t.png
f_23b, /emoji/f_23b.png
f_30b, /emoji/f_30b.png
f_32b, /emoji/f_32b.png
f_33b, /emoji/f_33b.png
f_33b00b, /emoji/f_33b00b.png
f_33b22b, /emoji/f_33b22b.png
f_33h, /emoji/f_33h.png
f_33t, /emoji/f_33t.png

View file

@ -14,9 +14,12 @@
# manifest is generated by the mix phoenix.digest task
# which you typically run after static files are built.
config :pleroma, Pleroma.Web.Endpoint,
on_init: {Pleroma.Web.Endpoint, :load_from_system_env, []},
url: [host: "example.com", port: 80],
cache_static_manifest: "priv/static/cache_manifest.json"
http: [port: 4000],
protocol: "http",
debug_errors: true,
code_reloader: true,
check_origin: false,
watchers: []
# Do not print debug messages in production
config :logger, level: :info

5
installation/Caddyfile Normal file
View file

@ -0,0 +1,5 @@
instance.example.com { # Your instance's domain
proxy / localhost:4000 {
websocket
}
}

View file

@ -1,3 +1,6 @@
proxy_cache_path /tmp/pleroma-media-cache levels=1:2 keys_zone=pleroma_media_cache:10m max_size=10g
inactive=720m use_temp_path=off;
server {
listen 80;
server_name example.tld;
@ -19,11 +22,17 @@ server {
server_name example.tld;
location / {
add_header 'Access-Control-Allow-Origin' '*';
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://localhost:4000;
}
include snippets/well-known.conf;
location /proxy {
proxy_cache pleroma_media_cache;
proxy_cache_lock on;
proxy_pass http://localhost:4000;
}
}

View file

@ -8,11 +8,20 @@ def run(_) do
domain = IO.gets("What is your domain name? (e.g. pleroma.soykaf.com): ") |> String.trim
name = IO.gets("What is the name of your instance? (e.g. Pleroma/Soykaf): ") |> String.trim
email = IO.gets("What's your admin email address: ") |> String.trim
mediaproxy = IO.gets("Do you want to activate the mediaproxy? (y/N): ")
|> String.trim()
|> String.downcase()
|> String.starts_with?("y")
proxy_url = if mediaproxy do
IO.gets("What is the mediaproxy's URL? (e.g. https://cache.example.com): ") |> String.trim
else
"https://cache.example.com"
end
secret = :crypto.strong_rand_bytes(64) |> Base.encode64 |> binary_part(0, 64)
dbpass = :crypto.strong_rand_bytes(64) |> Base.encode64 |> binary_part(0, 64)
resultSql = EEx.eval_file("lib/mix/tasks/sample_psql.eex", [dbpass: dbpass])
result = EEx.eval_file("lib/mix/tasks/sample_config.eex", [domain: domain, email: email, name: name, secret: secret, dbpass: dbpass])
result = EEx.eval_file("lib/mix/tasks/sample_config.eex", [domain: domain, email: email, name: name, secret: secret, mediaproxy: mediaproxy, proxy_url: proxy_url, dbpass: dbpass])
IO.puts("\nWriting config to config/generated_config.exs.\n\nCheck it and configure your database, then copy it to either config/dev.secret.exs or config/prod.secret.exs")
File.write("config/generated_config.exs", result)

View file

@ -10,6 +10,11 @@ config :pleroma, :instance,
limit: 5000,
registrations_open: true
config :pleroma, :media_proxy,
enabled: <%= mediaproxy %>,
redirect_on_failure: true,
base_url: "<%= proxy_url %>"
# Configure your database
config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres,

View file

@ -20,13 +20,18 @@ def start(_type, _args) do
limit: 2500
]]),
worker(Pleroma.Web.Federator, []),
worker(Pleroma.Web.ChatChannel.ChatChannelState, []),
worker(Pleroma.Stats, []),
]
++ if Mix.env == :test, do: [], else: [worker(Pleroma.Web.Streamer, [])]
++ if !chat_enabled(), do: [], else: [worker(Pleroma.Web.ChatChannel.ChatChannelState, [])]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Pleroma.Supervisor]
Supervisor.start_link(children, opts)
end
defp chat_enabled do
Application.get_env(:pleroma, :chat, []) |> Keyword.get(:enabled)
end
end

View file

@ -1,5 +1,6 @@
defmodule Pleroma.Formatter do
alias Pleroma.User
alias Pleroma.Web.MediaProxy
@link_regex ~r/https?:\/\/[\w\.\/?=\-#%&@~\(\)]+[\w\/]/u
def linkify(text) do
@ -10,7 +11,7 @@ def linkify(text) do
def parse_tags(text, data \\ %{}) do
Regex.scan(@tag_regex, text)
|> Enum.map(fn (["#" <> tag = full_tag]) -> {full_tag, String.downcase(tag)} end)
|> (fn map -> if data["sensitive"], do: [{"#nsfw", "nsfw"}] ++ map, else: map end).()
|> (fn map -> if data["sensitive"] in [true, "True", "true", "1"], do: [{"#nsfw", "nsfw"}] ++ map, else: map end).()
end
def parse_mentions(text) do
@ -103,12 +104,18 @@ def html_escape(text) do
{finmoji, "/finmoji/128px/#{finmoji}-128.png"}
end)
@emoji_from_file (with {:ok, file} <- File.read("config/emoji.txt") do
file
|> String.trim
|> String.split("\n")
@emoji_from_file (with {:ok, default} <- File.read("config/emoji.txt") do
custom =
with {:ok, custom} <- File.read("config/custom_emoji.txt") do
custom
else
_e -> ""
end
(default <> "\n" <> custom)
|> String.trim()
|> String.split(~r/\n+/)
|> Enum.map(fn(line) ->
[name, file] = String.split(line, ", ")
[name, file] = String.split(line, ~r/,\s*/)
{name, file}
end)
else
@ -125,7 +132,7 @@ def emojify(text, additional \\ nil) do
end
Enum.reduce(all_emoji, text, fn ({emoji, file}, text) ->
String.replace(text, ":#{emoji}:", "<img height='32px' width='32px' alt='#{emoji}' title='#{emoji}' src='#{file}' />")
String.replace(text, ":#{emoji}:", "<img height='32px' width='32px' alt='#{emoji}' title='#{emoji}' src='#{MediaProxy.url(file)}' />")
end)
end

14
lib/pleroma/http/http.ex Normal file
View file

@ -0,0 +1,14 @@
defmodule Pleroma.HTTP do
use HTTPoison.Base
def process_request_options(options) do
config = Application.get_env(:pleroma, :http, [])
proxy = Keyword.get(config, :proxy_url, nil)
case proxy do
nil -> options
_ -> options ++ [proxy: proxy]
end
end
end

41
lib/pleroma/stats.ex Normal file
View file

@ -0,0 +1,41 @@
defmodule Pleroma.Stats do
import Ecto.Query
alias Pleroma.{User, Repo, Activity}
def start_link do
agent = Agent.start_link(fn -> {[], %{}} end, name: __MODULE__)
spawn(fn -> schedule_update() end)
agent
end
def get_stats do
Agent.get(__MODULE__, fn {_, stats} -> stats end)
end
def get_peers do
Agent.get(__MODULE__, fn {peers, _} -> peers end)
end
def schedule_update do
spawn(fn ->
Process.sleep(1000 * 60 * 60 * 1) # 1 hour
schedule_update()
end)
update_stats()
end
def update_stats do
peers = from(u in Pleroma.User,
select: fragment("distinct ?->'host'", u.info),
where: u.local != ^true)
|> Repo.all()
domain_count = Enum.count(peers)
status_query = from(u in User.local_user_query,
select: fragment("sum((?->>'note_count')::int)", u.info))
status_count = Repo.one(status_query) |> IO.inspect
user_count = Repo.aggregate(User.local_user_query, :count, :id)
Agent.update(__MODULE__, fn _ ->
{peers, %{domain_count: domain_count, status_count: status_count, user_count: user_count}}
end)
end
end

View file

@ -9,7 +9,7 @@ def store(%Plug.Upload{} = file) do
File.cp!(file.path, result_file)
# fix content type on some image uploads
content_type = if file.content_type == "application/octet-stream" do
content_type = if file.content_type in [nil, "application/octet-stream"] do
get_content_type(file.path)
else
file.content_type

View file

@ -29,14 +29,14 @@ defmodule Pleroma.User do
def avatar_url(user) do
case user.avatar do
%{"url" => [%{"href" => href} | _]} -> href
_ -> "https://placehold.it/48x48"
_ -> "#{Web.base_url()}/images/avi.png"
end
end
def banner_url(user) do
case user.info["banner"] do
%{"url" => [%{"href" => href} | _]} -> href
_ -> nil
_ -> "#{Web.base_url()}/images/banner.png"
end
end

View file

@ -5,7 +5,9 @@ defmodule Pleroma.Web.UserSocket do
## Channels
# channel "room:*", Pleroma.Web.RoomChannel
if Application.get_env(:pleroma, :chat) |> Keyword.get(:enabled) do
channel "chat:*", Pleroma.Web.ChatChannel
end
## Transports
transport :websocket, Phoenix.Transports.WebSocket

View file

@ -24,7 +24,6 @@ def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}}
end
defmodule Pleroma.Web.ChatChannel.ChatChannelState do
use Agent
@max_messages 20
def start_link do

View file

@ -95,7 +95,7 @@ def add_user_links(text, mentions) do
Enum.reduce(mentions, step_one, fn ({match, %User{ap_id: ap_id}, uuid}, text) ->
short_match = String.split(match, "@") |> tl() |> hd()
String.replace(text, uuid, "<a href='#{ap_id}'>@#{short_match}</a>")
String.replace(text, uuid, "<span><a href='#{ap_id}'>@<span>#{short_match}</span></a></span>")
end)
end

View file

@ -1,7 +1,9 @@
defmodule Pleroma.Web.Endpoint do
use Phoenix.Endpoint, otp_app: :pleroma
if Application.get_env(:pleroma, :chat) |> Keyword.get(:enabled) do
socket "/socket", Pleroma.Web.UserSocket
end
socket "/api/v1", Pleroma.Web.MastodonAPI.MastodonSocket
# Serve at "/" the static files from "priv/static" directory.
@ -12,7 +14,7 @@ defmodule Pleroma.Web.Endpoint do
at: "/media", from: "uploads", gzip: false
plug Plug.Static,
at: "/", from: :pleroma,
only: ~w(index.html static finmoji emoji packs sounds sw.js)
only: ~w(index.html static finmoji emoji packs sounds images instance sw.js)
# Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint.

View file

@ -41,12 +41,12 @@ def handle(:request_subscription, websub) do
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
Logger.debug(fn -> "Sending #{activity.data["id"]} out via websub" end)
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
{:ok, actor} = WebFinger.ensure_keys_present(actor)
Logger.debug(fn -> "Sending #{activity.data["id"]} out via salmon" end)
Pleroma.Web.Salmon.publish(actor, activity)
Logger.debug(fn -> "Sending #{activity.data["id"]} out via websub" end)
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
end
end

View file

@ -1,6 +1,6 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller
alias Pleroma.{Repo, Activity, User, Notification}
alias Pleroma.{Repo, Activity, User, Notification, Stats}
alias Pleroma.Web
alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView}
alias Pleroma.Web.ActivityPub.ActivityPub
@ -93,7 +93,6 @@ def user(conn, %{"id" => id}) do
@instance Application.get_env(:pleroma, :instance)
def masto_instance(conn, _params) do
user_count = Repo.aggregate(User.local_user_query, :count, :id)
response = %{
uri: Web.base_url,
title: Keyword.get(@instance, :name),
@ -103,17 +102,18 @@ def masto_instance(conn, _params) do
urls: %{
streaming_api: String.replace(Web.base_url, ["http","https"], "wss")
},
stats: %{
status_count: 2,
user_count: user_count,
domain_count: 3
},
stats: Stats.get_stats,
thumbnail: Web.base_url <> "/instance/thumbnail.jpeg",
max_toot_chars: Keyword.get(@instance, :limit)
}
json(conn, response)
end
def peers(conn, _params) do
json(conn, Stats.get_peers)
end
defp mastodonized_emoji do
Pleroma.Formatter.get_custom_emoji()
|> Enum.map(fn {shortcode, relative_url} ->
@ -162,7 +162,7 @@ def home_timeline(%{assigns: %{user: user}} = conn, params) do
def public_timeline(%{assigns: %{user: user}} = conn, params) do
params = params
|> Map.put("type", ["Create", "Announce"])
|> Map.put("local_only", !!params["local"])
|> Map.put("local_only", params["local"] in [true, "True", "true", "1"])
|> Map.put("blocking_user", user)
activities = ActivityPub.fetch_public_activities(params)

View file

@ -3,20 +3,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
alias Pleroma.User
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.CommonAPI.Utils
defp image_url(%{"url" => [ %{ "href" => href } | _ ]}), do: href
defp image_url(_), do: nil
alias Pleroma.Web.MediaProxy
def render("accounts.json", %{users: users} = opts) do
render_many(users, AccountView, "account.json", opts)
end
def render("account.json", %{user: user}) do
image = User.avatar_url(user)
image = User.avatar_url(user) |> MediaProxy.url()
header = User.banner_url(user) |> MediaProxy.url()
user_info = User.user_info(user)
header = image_url(user.info["banner"]) || "https://placehold.it/700x335"
%{
id: to_string(user.id),
username: hd(String.split(user.nickname, "@")),

View file

@ -3,6 +3,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.Web.MastodonAPI.{AccountView, StatusView}
alias Pleroma.{User, Activity}
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MediaProxy
def render("index.json", opts) do
render_many(opts.activities, StatusView, "status.json", opts)
@ -121,9 +122,9 @@ def render("attachment.json", %{attachment: attachment}) do
%{
id: to_string(attachment["id"] || hash_id),
url: href,
url: MediaProxy.url(href),
remote_url: href,
preview_url: href,
preview_url: MediaProxy.url(href),
text_url: href,
type: type
}

View file

@ -0,0 +1,84 @@
defmodule Pleroma.Web.MediaProxy.MediaProxyController do
use Pleroma.Web, :controller
require Logger
@httpoison Application.get_env(:pleroma, :httpoison)
@max_body_length 25 * 1048576
@cache_control %{
default: "public, max-age=1209600",
error: "public, must-revalidate, max-age=160",
}
def remote(conn, %{"sig" => sig, "url" => url}) do
config = Application.get_env(:pleroma, :media_proxy, [])
with \
true <- Keyword.get(config, :enabled, false),
{:ok, url} <- Pleroma.Web.MediaProxy.decode_url(sig, url),
{:ok, content_type, body} <- proxy_request(url)
do
conn
|> put_resp_content_type(content_type)
|> set_cache_header(:default)
|> send_resp(200, body)
else
false -> send_error(conn, 404)
{:error, :invalid_signature} -> send_error(conn, 403)
{:error, {:http, _, url}} -> redirect_or_error(conn, url, Keyword.get(config, :redirect_on_failure, true))
end
end
defp proxy_request(link) do
headers = [{"user-agent", "Pleroma/MediaProxy; #{Pleroma.Web.base_url()} <#{Application.get_env(:pleroma, :instance)[:email]}>"}]
options = @httpoison.process_request_options([:insecure, {:follow_redirect, true}])
with \
{:ok, 200, headers, client} <- :hackney.request(:get, link, headers, "", options),
headers = Enum.into(headers, Map.new),
{:ok, body} <- proxy_request_body(client),
content_type <- proxy_request_content_type(headers, body)
do
{:ok, content_type, body}
else
{:ok, status, _, _} ->
Logger.warn "MediaProxy: request failed, status #{status}, link: #{link}"
{:error, {:http, :bad_status, link}}
{:error, error} ->
Logger.warn "MediaProxy: request failed, error #{inspect error}, link: #{link}"
{:error, {:http, error, link}}
end
end
defp set_cache_header(conn, key) do
Plug.Conn.put_resp_header(conn, "cache-control", @cache_control[key])
end
defp redirect_or_error(conn, url, true), do: redirect(conn, external: url)
defp redirect_or_error(conn, url, _), do: send_error(conn, 502, "Media proxy error: " <> url)
defp send_error(conn, code, body \\ "") do
conn
|> set_cache_header(:error)
|> send_resp(code, body)
end
defp proxy_request_body(client), do: proxy_request_body(client, <<>>)
defp proxy_request_body(client, body) when byte_size(body) < @max_body_length do
case :hackney.stream_body(client) do
{:ok, data} -> proxy_request_body(client, <<body :: binary, data :: binary>>)
:done -> {:ok, body}
{:error, reason} -> {:error, reason}
end
end
defp proxy_request_body(client, _) do
:hackney.close(client)
{:error, :body_too_large}
end
# TODO: the body is passed here as well because some hosts do not provide a content-type.
# At some point we may want to use magic numbers to discover the content-type and reply a proper one.
defp proxy_request_content_type(headers, _body) do
headers["Content-Type"] || headers["content-type"] || "image/jpeg"
end
end

View file

@ -0,0 +1,32 @@
defmodule Pleroma.Web.MediaProxy do
@base64_opts [padding: false]
def url(nil), do: nil
def url(url = "/" <> _), do: url
def url(url) do
config = Application.get_env(:pleroma, :media_proxy, [])
if !Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url) do
url
else
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
base64 = Base.url_encode64(url, @base64_opts)
sig = :crypto.hmac(:sha, secret, base64)
sig64 = sig |> Base.url_encode64(@base64_opts)
Keyword.get(config, :base_url, Pleroma.Web.base_url) <> "/proxy/#{sig64}/#{base64}"
end
end
def decode_url(sig, url) do
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
sig = Base.url_decode64!(sig, @base64_opts)
local_sig = :crypto.hmac(:sha, secret, url)
if local_sig == sig do
{:ok, Base.url_decode64!(url, @base64_opts)}
else
{:error, :invalid_signature}
end
end
end

View file

@ -0,0 +1,12 @@
defmodule Pleroma.Web.OAuth.FallbackController do
use Pleroma.Web, :controller
alias Pleroma.Web.OAuth.OAuthController
# No user/password
def call(conn, _) do
conn
|> put_flash(:error, "Invalid Username/Password")
|> OAuthController.authorize(conn.params)
end
end

View file

@ -5,6 +5,11 @@ defmodule Pleroma.Web.OAuth.OAuthController do
alias Pleroma.{Repo, User}
alias Comeonin.Pbkdf2
plug :fetch_session
plug :fetch_flash
action_fallback Pleroma.Web.OAuth.FallbackController
def authorize(conn, params) do
render conn, "show.html", %{
response_type: params["response_type"],

View file

@ -1,6 +1,8 @@
defmodule Pleroma.Web.OStatus.FeedRepresenter do
alias Pleroma.Web.OStatus
alias Pleroma.Web.OStatus.{UserRepresenter, ActivityRepresenter}
alias Pleroma.User
alias Pleroma.Web.MediaProxy
def to_simple_form(user, activities, _users) do
most_recent_update = (List.first(activities) || user).updated_at
@ -25,6 +27,7 @@ def to_simple_form(user, activities, _users) do
{:id, h.(OStatus.feed_path(user))},
{:title, ['#{user.nickname}\'s timeline']},
{:updated, h.(most_recent_update)},
{:logo, [to_charlist(User.avatar_url(user) |> MediaProxy.url())]},
{:link, [rel: 'hub', href: h.(OStatus.pubsub_path(user))], []},
{:link, [rel: 'salmon', href: h.(OStatus.salmon_path(user))], []},
{:link, [rel: 'self', href: h.(OStatus.feed_path(user)), type: 'application/atom+xml'], []},

View file

@ -22,6 +22,10 @@ def salmon_path(user) do
"#{user.ap_id}/salmon"
end
def remote_follow_path do
"#{Web.base_url}/ostatus_subscribe?acct={uri}"
end
def handle_incoming(xml_string) do
with doc when doc != :error <- parse_document(xml_string) do
entries = :xmerl_xpath.string('//entry', doc)
@ -159,8 +163,7 @@ def get_content(entry) do
Get the cw that mastodon uses.
"""
def get_cw(entry) do
with scope when not is_nil(scope) <- string_from_xpath("//mastodon:scope", entry),
cw when not is_nil(cw) <- string_from_xpath("/*/summary", entry) do
with cw when not is_nil(cw) <- string_from_xpath("/*/summary", entry) do
cw
else _e -> nil
end

View file

@ -28,6 +28,13 @@ def user_fetcher(username) do
plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true}
end
pipeline :pleroma_html do
plug :accepts, ["html"]
plug :fetch_session
plug Pleroma.Plugs.OAuthPlug
plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true}
end
pipeline :well_known do
plug :accepts, ["xml", "xrd+xml"]
end
@ -51,6 +58,18 @@ def user_fetcher(username) do
get "/emoji", UtilController, :emoji
end
scope "/", Pleroma.Web.TwitterAPI do
pipe_through :pleroma_html
get "/ostatus_subscribe", UtilController, :remote_follow
post "/ostatus_subscribe", UtilController, :do_remote_follow
post "/main/ostatus", UtilController, :remote_subscribe
end
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
pipe_through :authenticated_api
post "/follow_import", UtilController, :follow_import
end
scope "/oauth", Pleroma.Web.OAuth do
get "/authorize", OAuthController, :authorize
post "/authorize", OAuthController, :create_authorization
@ -101,6 +120,7 @@ def user_fetcher(username) do
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through :api
get "/instance", MastodonAPIController, :masto_instance
get "/instance/peers", MastodonAPIController, :peers
post "/apps", MastodonAPIController, :create_app
get "/custom_emojis", MastodonAPIController, :custom_emojis
@ -142,6 +162,8 @@ def user_fetcher(username) do
get "/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline
get "/users/show", TwitterAPI.Controller, :show_user
get "/statuses/followers", TwitterAPI.Controller, :followers
get "/statuses/friends", TwitterAPI.Controller, :friends
get "/statuses/show/:id", TwitterAPI.Controller, :fetch_status
get "/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation
@ -188,8 +210,6 @@ def user_fetcher(username) do
post "/qvitter/update_avatar", TwitterAPI.Controller, :update_avatar
get "/statuses/followers", TwitterAPI.Controller, :followers
get "/statuses/friends", TwitterAPI.Controller, :friends
get "/friends/ids", TwitterAPI.Controller, :friends_ids
get "/friendships/no_retweets/ids", TwitterAPI.Controller, :empty_array
@ -243,6 +263,14 @@ def user_fetcher(username) do
delete "/auth/sign_out", MastodonAPIController, :logout
end
pipeline :remote_media do
plug :accepts, ["html"]
end
scope "/proxy/", Pleroma.Web.MediaProxy do
pipe_through :remote_media
get "/:sig/:url", MediaProxyController, :remote
end
scope "/", Fallback do
get "/*path", RedirectController, :redirector
end

View file

@ -1,3 +1,5 @@
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<h2>OAuth Authorization</h2>
<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
<%= label f, :name, "Name" %>

View file

@ -0,0 +1,11 @@
<%= if @error == :error do %>
<h2>Error fetching user</h2>
<% else %>
<h2>Remote follow</h2>
<img width="128" height="128" src="<%= @avatar %>">
<p><%= @name %></p>
<%= form_for @conn, util_path(@conn, :do_remote_follow), [as: "user"], fn f -> %>
<%= hidden_input f, :id, value: @id %>
<%= submit "Authorize" %>
<% end %>
<% end %>

View file

@ -0,0 +1,14 @@
<%= if @error do %>
<h2><%= @error %></h2>
<% end %>
<h2>Log in to follow</h2>
<p><%= @name %></p>
<img height="128" width="128" src="<%= @avatar %>">
<%= form_for @conn, util_path(@conn, :do_remote_follow), [as: "authorization"], fn f -> %>
<%= text_input f, :name, placeholder: "Username" %>
<br>
<%= password_input f, :password, placeholder: "Password" %>
<br>
<%= hidden_input f, :id, value: @id %>
<%= submit "Authorize" %>
<% end %>

View file

@ -0,0 +1,6 @@
<%= if @error do %>
<p>Error following account</p>
<% else %>
<h2>Account followed!</h2>
<% end %>

View file

@ -0,0 +1,10 @@
<%= if @error do %>
<h2>Error: <%= @error %></h2>
<% else %>
<h2>Remotely follow <%= @nickname %></h2>
<%= form_for @conn, util_path(@conn, :remote_subscribe), [as: "user"], fn f -> %>
<%= hidden_input f, :nickname, value: @nickname %>
<%= text_input f, :profile, placeholder: "Your account ID, e.g. lain@quitter.se" %>
<%= submit "Follow" %>
<% end %>
<% end %>

View file

@ -1,8 +1,12 @@
defmodule Pleroma.Web.TwitterAPI.UtilController do
use Pleroma.Web, :controller
require Logger
alias Pleroma.Web
alias Pleroma.Web.OStatus
alias Pleroma.Web.WebFinger
alias Comeonin.Pbkdf2
alias Pleroma.Formatter
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.{Repo, PasswordResetToken, User}
def show_password_reset(conn, %{"token" => token}) do
@ -29,6 +33,72 @@ def help_test(conn, _params) do
json(conn, "ok")
end
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
with %User{} = user <- User.get_cached_by_nickname(nick),
avatar = User.avatar_url(user) do
conn
|> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
else
_e -> render(conn, "subscribe.html", %{nickname: nick, avatar: nil, error: "Could not find user"})
end
end
def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do
with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
%User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do
conn
|> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id))
else
_e ->
render(conn, "subscribe.html", %{nickname: nick, avatar: nil, error: "Something went wrong."})
end
end
def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
{err, followee} = OStatus.find_or_make_user(acct)
avatar = User.avatar_url(followee)
name = followee.nickname
id = followee.id
if !!user do
conn
|> render("follow.html", %{error: err, acct: acct, avatar: avatar, name: name, id: id})
else
conn
|> render("follow_login.html", %{error: false, acct: acct, avatar: avatar, name: name, id: id})
end
end
def do_remote_follow(conn, %{"authorization" => %{"name" => username, "password" => password, "id" => id}}) do
followee = Repo.get(User, id)
avatar = User.avatar_url(followee)
name = followee.nickname
with %User{} = user <- User.get_cached_by_nickname(username),
true <- Pbkdf2.checkpw(password, user.password_hash),
%User{} = followed <- Repo.get(User, id),
{:ok, follower} <- User.follow(user, followee),
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
conn
|> render("followed.html", %{error: false})
else
_e ->
conn
|> render("follow_login.html", %{error: "Wrong username or password", id: id, name: name, avatar: avatar})
end
end
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
with %User{} = followee <- Repo.get(User, id),
{:ok, follower} <- User.follow(user, followee),
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
conn
|> render("followed.html", %{error: false})
else
e ->
Logger.debug("Remote follow failed with error #{inspect e}")
conn
|> render("followed.html", %{error: inspect(e)})
end
end
@instance Application.get_env(:pleroma, :instance)
def config(conn, _params) do
case get_format(conn) do
@ -51,7 +121,7 @@ def config(conn, _params) do
site: %{
name: Keyword.get(@instance, :name),
server: Web.base_url,
textlimit: Keyword.get(@instance, :limit),
textlimit: to_string(Keyword.get(@instance, :limit)),
closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1")
}
})
@ -73,4 +143,24 @@ def version(conn, _params) do
def emoji(conn, _params) do
json conn, Enum.into(Formatter.get_custom_emoji(), %{})
end
def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
follow_import(conn, %{"list" => File.read!(listfile.path)})
end
def follow_import(%{assigns: %{user: user}} = conn, %{"list" => list}) do
Task.start(fn ->
String.split(list)
|> Enum.map(fn nick ->
with %User{} = follower <- User.get_cached_by_ap_id(user.ap_id),
%User{} = followed <- User.get_or_fetch_by_nickname(nick),
{:ok, follower} <- User.follow(follower, followed) do
ActivityPub.follow(follower, followed)
else
_e -> Logger.debug "follow_import: following #{nick} failed"
end
end)
end)
json conn, "job started"
end
end

View file

@ -6,7 +6,7 @@ def to_map(%Object{} = object, _opts) do
data = object.data
url = List.first(data["url"])
%{
url: url["href"],
url: url["href"] |> Pleroma.Web.MediaProxy.url(),
mimetype: url["mediaType"],
id: data["uuid"],
oembed: false

View file

@ -316,10 +316,12 @@ def conversation_id_to_context(id) do
def get_external_profile(for_user, uri) do
with {:ok, %User{} = user} <- OStatus.find_or_make_user(uri) do
spawn(fn ->
with url <- user.info["topic"],
{:ok, %{body: body}} <- @httpoison.get(url, [], follow_redirect: true, timeout: 10000, recv_timeout: 20000) do
OStatus.handle_incoming(body)
end
end)
{:ok, UserView.render("show.json", %{user: user, for: for_user})}
else _e ->
{:error, "Couldn't find user"}

View file

@ -263,16 +263,18 @@ def update_most_recent_notification(%{assigns: %{user: user}} = conn, %{"id" =>
end
end
def followers(%{assigns: %{user: user}} = conn, _params) do
with {:ok, followers} <- User.get_followers(user) do
def followers(conn, params) do
with {:ok, user} <- TwitterAPI.get_user(conn.assigns.user, params),
{:ok, followers} <- User.get_followers(user) do
render(conn, UserView, "index.json", %{users: followers, for: user})
else
_e -> bad_request_reply(conn, "Can't get followers")
end
end
def friends(%{assigns: %{user: user}} = conn, _params) do
with {:ok, friends} <- User.get_friends(user) do
def friends(conn, params) do
with {:ok, user} <- TwitterAPI.get_user(conn.assigns.user, params),
{:ok, friends} <- User.get_friends(user) do
render(conn, UserView, "index.json", %{users: friends, for: user})
else
_e -> bad_request_reply(conn, "Can't get friends")

View file

@ -2,6 +2,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
use Pleroma.Web, :view
alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MediaProxy
def render("show.json", %{user: user = %User{}} = assigns) do
render_one(user, Pleroma.Web.TwitterAPI.UserView, "user.json", assigns)
@ -12,7 +13,7 @@ def render("index.json", %{users: users, for: user}) do
end
def render("user.json", %{user: user = %User{}} = assigns) do
image = User.avatar_url(user)
image = User.avatar_url(user) |> MediaProxy.url()
{following, follows_you, statusnet_blocking} = if assigns[:for] do
{
User.following?(assigns[:for], user),
@ -44,8 +45,9 @@ def render("user.json", %{user: user = %User{}} = assigns) do
"screen_name" => user.nickname,
"statuses_count" => user_info[:note_count],
"statusnet_profile_url" => user.ap_id,
"cover_photo" => image_url(user.info["banner"]),
"background_image" => image_url(user.info["background"])
"cover_photo" => User.banner_url(user) |> MediaProxy.url(),
"background_image" => image_url(user.info["background"]) |> MediaProxy.url(),
"is_local" => user.local
}
if assigns[:token] do

View file

@ -45,7 +45,8 @@ 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: "self", type: "application/activity+json", href: user.ap_id}},
{:Link, %{rel: "http://ostatus.org/schema/1.0/subscribe", template: OStatus.remote_follow_path()}}
]
}
|> XmlBuilder.to_doc
@ -69,11 +70,13 @@ defp webfinger_from_xml(doc) do
topic = XML.string_from_xpath(~s{//Link[@rel="http://schemas.google.com/g/2010#updates-from"]/@href}, doc)
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)
data = %{
"magic_key" => magic_key,
"topic" => topic,
"subject" => subject,
"salmon" => salmon
"salmon" => salmon,
"subscribe_address" => subscribe_address
}
{:ok, data}
end

BIN
priv/static/emoji/blank.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

BIN
priv/static/emoji/f_00b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
priv/static/emoji/f_00h.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
priv/static/emoji/f_00t.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
priv/static/emoji/f_01b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
priv/static/emoji/f_03b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
priv/static/emoji/f_10b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
priv/static/emoji/f_11b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
priv/static/emoji/f_11h.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
priv/static/emoji/f_11t.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
priv/static/emoji/f_12b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
priv/static/emoji/f_21b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
priv/static/emoji/f_22b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
priv/static/emoji/f_22h.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
priv/static/emoji/f_22t.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
priv/static/emoji/f_23b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
priv/static/emoji/f_30b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
priv/static/emoji/f_32b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
priv/static/emoji/f_33b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
priv/static/emoji/f_33h.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
priv/static/emoji/f_33t.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
priv/static/images/avi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.67f64792f89a96e59442c437c7ded0b3.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.ee87253244897e08bdce.js></script><script type=text/javascript src=/static/js/vendor.50cd70f77f559bfe1f27.js></script><script type=text/javascript src=/static/js/app.fefccf252cac9e1310ea.js></script></body></html>
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.b3deb1dd44970d86cc6b368f36fd09d9.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.15dfe939c498cca9840c.js></script><script type=text/javascript src=/static/js/vendor.409059e5a814f448f5bc.js></script><script type=text/javascript src=/static/js/app.30c01d7540d43b760f03.js></script></body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View file

@ -1,7 +1,7 @@
{
"name": "Pleroma FE",
"theme": "pleroma-dark",
"background": "/static/bg.jpg",
"logo": "/static/logo.png",
"registrationOpen": true
"defaultPath": "/main/all",
"chatDisabled": false
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var r=window.webpackJsonp;window.webpackJsonp=function(o,c){for(var p,l,s=0,i=[];s<o.length;s++)l=o[s],a[l]&&i.push.apply(i,a[l]),a[l]=0;for(p in c)Object.prototype.hasOwnProperty.call(c,p)&&(e[p]=c[p]);for(r&&r(o,c);i.length;)i.shift().call(null,t);if(c[0])return n[0]=0,t(0)};var n={},a={0:0};t.e=function(e,r){if(0===a[e])return r.call(null,t);if(void 0!==a[e])a[e].push(r);else{a[e]=[r];var n=document.getElementsByTagName("head")[0],o=document.createElement("script");o.type="text/javascript",o.charset="utf-8",o.async=!0,o.src=t.p+"static/js/"+e+"."+{1:"409059e5a814f448f5bc",2:"30c01d7540d43b760f03"}[e]+".js",n.appendChild(o)}},t.m=e,t.c=n,t.p="/"}([]);
//# sourceMappingURL=manifest.15dfe939c498cca9840c.js.map

File diff suppressed because one or more lines are too long

View file

@ -1,2 +0,0 @@
!function(e){function t(n){if(r[n])return r[n].exports;var a=r[n]={exports:{},id:n,loaded:!1};return e[n].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n=window.webpackJsonp;window.webpackJsonp=function(c,o){for(var f,p,s=0,l=[];s<c.length;s++)p=c[s],a[p]&&l.push.apply(l,a[p]),a[p]=0;for(f in o)e[f]=o[f];for(n&&n(c,o);l.length;)l.shift().call(null,t);if(o[0])return r[0]=0,t(0)};var r={},a={0:0};t.e=function(e,n){if(0===a[e])return n.call(null,t);if(void 0!==a[e])a[e].push(n);else{a[e]=[n];var r=document.getElementsByTagName("head")[0],c=document.createElement("script");c.type="text/javascript",c.charset="utf-8",c.async=!0,c.src=t.p+"static/js/"+e+"."+{1:"50cd70f77f559bfe1f27",2:"fefccf252cac9e1310ea"}[e]+".js",r.appendChild(c)}},t.m=e,t.c=r,t.p="/"}([]);
//# sourceMappingURL=manifest.ee87253244897e08bdce.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -14,9 +14,9 @@ test "notifies someone when they are directly addressed" do
{:ok, [notification, other_notification]} = Notification.create_notifications(activity)
assert notification.user_id == other_user.id
notified_ids = Enum.sort([notification.user_id, other_notification.user_id])
assert notified_ids == [other_user.id, third_user.id]
assert notification.activity_id == activity.id
assert other_notification.user_id == third_user.id
assert other_notification.activity_id == activity.id
end
end

View file

@ -19,10 +19,10 @@ test "Represent a user account" do
statuses_count: 5,
note: user.bio,
url: user.ap_id,
avatar: "https://placehold.it/48x48",
avatar_static: "https://placehold.it/48x48",
header: "https://placehold.it/700x335",
header_static: "https://placehold.it/700x335",
avatar: "http://localhost:4001/images/avi.png",
avatar_static: "http://localhost:4001/images/avi.png",
header: "http://localhost:4001/images/banner.png",
header_static: "http://localhost:4001/images/banner.png",
source: %{
note: "",
privacy: "public",

View file

@ -35,7 +35,7 @@ test "the public timeline", %{conn: conn} do
{:ok, [_activity]} = OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
conn = conn
|> get("/api/v1/timelines/public")
|> get("/api/v1/timelines/public", %{"local" => "False"})
assert length(json_response(conn, 200)) == 2
@ -43,6 +43,11 @@ test "the public timeline", %{conn: conn} do
|> get("/api/v1/timelines/public", %{"local" => "True"})
assert [%{"content" => "test"}] = json_response(conn, 200)
conn = build_conn()
|> get("/api/v1/timelines/public", %{"local" => "1"})
assert [%{"content" => "test"}] = json_response(conn, 200)
end
test "posting a status", %{conn: conn} do
@ -50,9 +55,9 @@ test "posting a status", %{conn: conn} do
conn = conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{"status" => "cofe", "spoiler_text" => "2hu"})
|> post("/api/v1/statuses", %{"status" => "cofe", "spoiler_text" => "2hu", "sensitive" => "false"})
assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu"} = json_response(conn, 200)
assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} = json_response(conn, 200)
assert Repo.get(Activity, id)
end
@ -145,7 +150,7 @@ test "list of notifications", %{conn: conn} do
|> assign(:user, user)
|> get("/api/v1/notifications")
expected_response = "hi <a href=\"#{user.ap_id}\">@#{user.nickname}</a>"
expected_response = "hi <span><a href=\"#{user.ap_id}\">@<span>#{user.nickname}</span></a></span>"
assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
assert response == expected_response
end
@ -161,7 +166,7 @@ test "getting a single notification", %{conn: conn} do
|> assign(:user, user)
|> get("/api/v1/notifications/#{notification.id}")
expected_response = "hi <a href=\"#{user.ap_id}\">@#{user.nickname}</a>"
expected_response = "hi <span><a href=\"#{user.ap_id}\">@<span>#{user.nickname}</span></a></span>"
assert %{"status" => %{"content" => response}} = json_response(conn, 200)
assert response == expected_response
end
@ -581,11 +586,14 @@ test "get instance information" do
{:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
Pleroma.Stats.update_stats()
conn = conn
|> get("/api/v1/instance")
assert result = json_response(conn, 200)
assert result["stats"]["user_count"] == 2
assert result["stats"]["status_count"] == 1
end
end

View file

@ -56,7 +56,9 @@ test "a note activity" do
test "contains mentions" do
incoming = File.read!("test/fixtures/incoming_reply_mastodon.xml")
user = insert(:user, %{ap_id: "https://pleroma.soykaf.com/users/lain"})
# a user with this ap id might be in the cache.
recipient = "https://pleroma.soykaf.com/users/lain"
user = User.get_cached_by_ap_id(recipient) || insert(:user, %{ap_id: recipient})
{:ok, [activity]} = OStatus.handle_incoming(incoming)

View file

@ -26,6 +26,7 @@ test "returns a feed of the last 20 items of the user" do
<id>#{OStatus.feed_path(user)}</id>
<title>#{user.nickname}'s timeline</title>
<updated>#{most_recent_update}</updated>
<logo>#{User.avatar_url(user)}</logo>
<link rel="hub" href="#{OStatus.pubsub_path(user)}" />
<link rel="salmon" href="#{OStatus.salmon_path(user)}" />
<link rel="self" href="#{OStatus.feed_path(user)}" type="application/atom+xml" />

View file

@ -302,7 +302,8 @@ test "it returns user info in a hash" do
"host" => "social.heldscal.la",
"fqn" => user,
"bio" => "cofe",
"avatar" => %{"type" => "Image", "url" => [%{"href" => "https://social.heldscal.la/avatar/29191-original-20170421154949.jpeg", "mediaType" => "image/jpeg", "type" => "Link"}]}
"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}"
}
assert data == expected
end
@ -325,7 +326,8 @@ test "it works with the uri" do
"host" => "social.heldscal.la",
"fqn" => user,
"bio" => "cofe",
"avatar" => %{"type" => "Image", "url" => [%{"href" => "https://social.heldscal.la/avatar/29191-original-20170421154949.jpeg", "mediaType" => "image/jpeg", "type" => "Link"}]}
"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}"
}
assert data == expected
end

View file

@ -21,6 +21,7 @@ test "returns a user with id, uri, name and link" do
<summary>#{user.bio}</summary>
<name>#{user.nickname}</name>
<link rel="avatar" href="#{User.avatar_url(user)}" />
<link rel="header" href="#{User.banner_url(user)}" />
"""
assert clean(res) == clean(expected)

View file

@ -518,7 +518,7 @@ test "it returns a user's followers", %{conn: conn} do
end
describe "GET /api/statuses/friends" do
test "it returns a user's friends", %{conn: conn} do
test "it returns the logged in user's friends", %{conn: conn} do
user = insert(:user)
followed_one = insert(:user)
followed_two = insert(:user)
@ -533,6 +533,36 @@ test "it returns a user's friends", %{conn: conn} do
assert MapSet.equal?(MapSet.new(json_response(conn, 200)), MapSet.new(UserView.render("index.json", %{users: [followed_one, followed_two], for: user})))
end
test "it returns a given user's friends with user_id", %{conn: conn} do
user = insert(:user)
followed_one = insert(:user)
followed_two = insert(:user)
not_followed = insert(:user)
{:ok, user} = User.follow(user, followed_one)
{:ok, user} = User.follow(user, followed_two)
conn = conn
|> get("/api/statuses/friends", %{"user_id" => user.id})
assert MapSet.equal?(MapSet.new(json_response(conn, 200)), MapSet.new(UserView.render("index.json", %{users: [followed_one, followed_two], for: user})))
end
test "it returns a given user's friends with screen_name", %{conn: conn} do
user = insert(:user)
followed_one = insert(:user)
followed_two = insert(:user)
not_followed = insert(:user)
{:ok, user} = User.follow(user, followed_one)
{:ok, user} = User.follow(user, followed_two)
conn = conn
|> get("/api/statuses/friends", %{"screen_name" => user.nickname})
assert MapSet.equal?(MapSet.new(json_response(conn, 200)), MapSet.new(UserView.render("index.json", %{users: [followed_one, followed_two], for: user})))
end
end
describe "GET /friends/ids" do

View file

@ -34,7 +34,7 @@ test "create a status" do
{ :ok, activity = %Activity{} } = TwitterAPI.create_status(user, input)
assert get_in(activity.data, ["object", "content"]) == "Hello again, <a href='shp'>@shp</a>.&lt;script&gt;&lt;/script&gt;<br>This is on another :moominmamma: line. #2hu #epic #phantasmagoric<br><a href=\"http://example.org/image.jpg\" class='attachment'>image.jpg</a>"
assert get_in(activity.data, ["object", "content"]) == "Hello again, <span><a href='shp'>@<span>shp</span></a></span>.&lt;script&gt;&lt;/script&gt;<br>This is on another :moominmamma: line. #2hu #epic #phantasmagoric<br><a href=\"http://example.org/image.jpg\" class='attachment'>image.jpg</a>"
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
@ -291,7 +291,7 @@ test "it adds user links to an existing text" do
archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"})
mentions = Pleroma.Formatter.parse_mentions(text)
expected_text = "<a href='#{gsimg.ap_id}'>@gsimg</a> According to <a href='#{archaeme.ap_id}'>@archaeme</a>, that is @daggsy. Also hello <a href='#{archaeme_remote.ap_id}'>@archaeme</a>"
expected_text = "<span><a href='#{gsimg.ap_id}'>@<span>gsimg</span></a></span> According to <span><a href='#{archaeme.ap_id}'>@<span>archaeme</span></a></span>, that is @daggsy. Also hello <span><a href='#{archaeme_remote.ap_id}'>@<span>archaeme</span></a></span>"
assert Utils.add_user_links(text, mentions) == expected_text
end
@ -404,7 +404,7 @@ test "fetches a user by uri" do
assert represented["id"] == UserView.render("show.json", %{user: remote, for: user})["id"]
# Also fetches the feed.
assert Activity.get_create_activity_by_object_ap_id("tag:mastodon.social,2017-04-05:objectId=1641750:objectType=Status")
# assert Activity.get_create_activity_by_object_ap_id("tag:mastodon.social,2017-04-05:objectId=1641750:objectType=Status")
end
end
end

Some files were not shown because too many files have changed in this diff Show more