Merge branch 'develop' into feature/activitypub
This commit is contained in:
commit
30e9b22f96
2938 changed files with 211846 additions and 378 deletions
22
lib/mix/tasks/generate_config.ex
Normal file
22
lib/mix/tasks/generate_config.ex
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
defmodule Mix.Tasks.GenerateConfig do
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "Generates a new config"
|
||||
def run(_) do
|
||||
IO.puts("Answer a few questions to generate a new config\n")
|
||||
IO.puts("--- THIS WILL OVERWRITE YOUR config/generated_config.exs! ---\n")
|
||||
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
|
||||
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])
|
||||
|
||||
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)
|
||||
IO.puts("\nWriting setup_db.psql, please run it as postgre superuser, i.e.: sudo su postgres -c 'psql -f config/setup_db.psql'")
|
||||
File.write("config/setup_db.psql", resultSql)
|
||||
end
|
||||
end
|
||||
20
lib/mix/tasks/sample_config.eex
Normal file
20
lib/mix/tasks/sample_config.eex
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use Mix.Config
|
||||
|
||||
config :pleroma, Pleroma.Web.Endpoint,
|
||||
url: [host: "<%= domain %>", scheme: "https", port: 443],
|
||||
secret_key_base: "<%= secret %>"
|
||||
|
||||
config :pleroma, :instance,
|
||||
name: "<%= name %>",
|
||||
email: "<%= email %>",
|
||||
limit: 5000,
|
||||
registrations_open: true
|
||||
|
||||
# Configure your database
|
||||
config :pleroma, Pleroma.Repo,
|
||||
adapter: Ecto.Adapters.Postgres,
|
||||
username: "pleroma",
|
||||
password: "<%= dbpass %>",
|
||||
database: "pleroma_dev",
|
||||
hostname: "localhost",
|
||||
pool_size: 10
|
||||
8
lib/mix/tasks/sample_psql.eex
Normal file
8
lib/mix/tasks/sample_psql.eex
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
CREATE USER pleroma WITH ENCRYPTED PASSWORD '<%= dbpass %>' CREATEDB;
|
||||
-- in case someone runs this second time accidentally
|
||||
ALTER USER pleroma WITH ENCRYPTED PASSWORD '<%= dbpass %>' CREATEDB;
|
||||
CREATE DATABASE pleroma_dev;
|
||||
ALTER DATABASE pleroma_dev OWNER TO pleroma;
|
||||
\c pleroma_dev;
|
||||
--Extensions made by ecto.migrate that need superuser access
|
||||
CREATE EXTENSION IF NOT EXISTS citext;
|
||||
44
lib/pleroma/PasswordResetToken.ex
Normal file
44
lib/pleroma/PasswordResetToken.ex
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
defmodule Pleroma.PasswordResetToken do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Pleroma.{User, PasswordResetToken, Repo}
|
||||
|
||||
schema "password_reset_tokens" do
|
||||
belongs_to :user, User
|
||||
field :token, :string
|
||||
field :used, :boolean, default: false
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def create_token(%User{} = user) do
|
||||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64
|
||||
|
||||
token = %PasswordResetToken{
|
||||
user_id: user.id,
|
||||
used: false,
|
||||
token: token
|
||||
}
|
||||
|
||||
Repo.insert(token)
|
||||
end
|
||||
|
||||
def used_changeset(struct) do
|
||||
struct
|
||||
|> cast(%{}, [])
|
||||
|> put_change(:used, true)
|
||||
end
|
||||
|
||||
def reset_password(token, data) do
|
||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||
%User{} = user <- Repo.get(User, token.user_id),
|
||||
{:ok, _user} <- User.reset_password(user, data),
|
||||
{:ok, token} <- Repo.update(used_changeset(token)) do
|
||||
{:ok, token}
|
||||
else
|
||||
_e -> {:error, token}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -6,7 +6,8 @@ defmodule Pleroma.Activity do
|
|||
schema "activities" do
|
||||
field :data, :map
|
||||
field :local, :boolean, default: true
|
||||
has_many :notifications, Notification
|
||||
field :actor, :string
|
||||
has_many :notifications, Notification, on_delete: :delete_all
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
|
@ -16,24 +17,29 @@ defmodule Pleroma.Activity do
|
|||
where: fragment("(?)->>'id' = ?", activity.data, ^to_string(ap_id)))
|
||||
end
|
||||
|
||||
# TODO:
|
||||
# Go through these and fix them everywhere.
|
||||
# Wrong name, only returns create activities
|
||||
def all_by_object_ap_id_q(ap_id) do
|
||||
from activity in Activity,
|
||||
where: fragment("(?)->'object'->>'id' = ?", activity.data, ^to_string(ap_id))
|
||||
where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ?", activity.data, activity.data, ^to_string(ap_id)),
|
||||
where: fragment("(?)->>'type' = 'Create'", activity.data)
|
||||
end
|
||||
|
||||
# Wrong name, returns all.
|
||||
def all_non_create_by_object_ap_id_q(ap_id) do
|
||||
from activity in Activity,
|
||||
where: fragment("(?)->>'object' = ?", activity.data, ^to_string(ap_id))
|
||||
where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ?", activity.data, activity.data, ^to_string(ap_id))
|
||||
end
|
||||
|
||||
# Wrong name plz fix thx
|
||||
def all_by_object_ap_id(ap_id) do
|
||||
Repo.all(all_by_object_ap_id_q(ap_id))
|
||||
end
|
||||
|
||||
def get_create_activity_by_object_ap_id(ap_id) do
|
||||
Repo.one(from activity in Activity,
|
||||
where: fragment("(?)->'object'->>'id' = ?", activity.data, ^to_string(ap_id))
|
||||
and fragment("(?)->>'type' = 'Create'", activity.data))
|
||||
where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ?", activity.data, activity.data, ^to_string(ap_id)),
|
||||
where: fragment("(?)->>'type' = 'Create'", activity.data))
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19,8 +19,10 @@ defmodule Pleroma.Application do
|
|||
ttl_interval: 1000,
|
||||
limit: 2500
|
||||
]]),
|
||||
worker(Pleroma.Web.Federator, [])
|
||||
worker(Pleroma.Web.Federator, []),
|
||||
worker(Pleroma.Web.ChatChannel.ChatChannelState, []),
|
||||
]
|
||||
++ if Mix.env == :test, do: [], else: [worker(Pleroma.Web.Streamer, [])]
|
||||
|
||||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
defmodule Pleroma.Formatter do
|
||||
alias Pleroma.User
|
||||
|
||||
@link_regex ~r/https?:\/\/[\w\.\/?=\-#%&]+[\w]/u
|
||||
@link_regex ~r/https?:\/\/[\w\.\/?=\-#%&@~\(\)]+[\w\/]/u
|
||||
def linkify(text) do
|
||||
Regex.replace(@link_regex, text, "<a href='\\0'>\\0</a>")
|
||||
end
|
||||
|
||||
@tag_regex ~r/\#\w+/u
|
||||
def parse_tags(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).()
|
||||
end
|
||||
|
||||
def parse_mentions(text) do
|
||||
|
|
@ -23,6 +24,15 @@ defmodule Pleroma.Formatter do
|
|||
|> Enum.filter(fn ({_match, user}) -> user end)
|
||||
end
|
||||
|
||||
def html_escape(text) do
|
||||
Regex.split(@link_regex, text, include_captures: true)
|
||||
|> Enum.map_every(2, fn chunk ->
|
||||
{:safe, part} = Phoenix.HTML.html_escape(chunk)
|
||||
part
|
||||
end)
|
||||
|> Enum.join("")
|
||||
end
|
||||
|
||||
@finmoji [
|
||||
"a_trusted_friend",
|
||||
"alandislands",
|
||||
|
|
@ -122,4 +132,8 @@ defmodule Pleroma.Formatter do
|
|||
def get_emoji(text) do
|
||||
Enum.filter(@emoji, fn ({emoji, _}) -> String.contains?(text, ":#{emoji}:") end)
|
||||
end
|
||||
|
||||
def get_custom_emoji() do
|
||||
@emoji
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -36,7 +36,38 @@ defmodule Pleroma.Notification do
|
|||
Repo.all(query)
|
||||
end
|
||||
|
||||
def create_notifications(%Activity{id: id, data: %{"to" => to, "type" => type}} = activity) when type in ["Create", "Like", "Announce", "Follow"] do
|
||||
def get(%{id: user_id} = _user, id) do
|
||||
query = from n in Notification,
|
||||
where: n.id == ^id,
|
||||
preload: [:activity]
|
||||
|
||||
notification = Repo.one(query)
|
||||
case notification do
|
||||
%{user_id: ^user_id} ->
|
||||
{:ok, notification}
|
||||
_ ->
|
||||
{:error, "Cannot get notification"}
|
||||
end
|
||||
end
|
||||
|
||||
def clear(user) do
|
||||
query = from n in Notification,
|
||||
where: n.user_id == ^user.id
|
||||
|
||||
Repo.delete_all(query)
|
||||
end
|
||||
|
||||
def dismiss(%{id: user_id} = _user, id) do
|
||||
notification = Repo.get(Notification, id)
|
||||
case notification do
|
||||
%{user_id: ^user_id} ->
|
||||
Repo.delete(notification)
|
||||
_ ->
|
||||
{:error, "Cannot dismiss notification"}
|
||||
end
|
||||
end
|
||||
|
||||
def create_notifications(%Activity{id: _, data: %{"to" => _, "type" => type}} = activity) when type in ["Create", "Like", "Announce", "Follow"] do
|
||||
users = User.get_notified_from_activity(activity)
|
||||
|
||||
notifications = Enum.map(users, fn (user) -> create_notification(activity, user) end)
|
||||
|
|
@ -46,9 +77,12 @@ defmodule Pleroma.Notification do
|
|||
|
||||
# TODO move to sql, too.
|
||||
def create_notification(%Activity{} = activity, %User{} = user) do
|
||||
notification = %Notification{user_id: user.id, activity_id: activity.id}
|
||||
{:ok, notification} = Repo.insert(notification)
|
||||
notification
|
||||
unless User.blocks?(user, %{ap_id: activity.data["actor"]}) do
|
||||
notification = %Notification{user_id: user.id, activity: activity}
|
||||
{:ok, notification} = Repo.insert(notification)
|
||||
Pleroma.Web.Streamer.stream("user", notification)
|
||||
notification
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -15,15 +15,16 @@ defmodule Pleroma.Object do
|
|||
end
|
||||
|
||||
def change(struct, params \\ %{}) do
|
||||
changeset = struct
|
||||
struct
|
||||
|> cast(params, [:data])
|
||||
|> validate_required([:data])
|
||||
|> unique_constraint(:ap_id, name: :objects_unique_apid_index)
|
||||
end
|
||||
|
||||
def get_by_ap_id(nil), do: nil
|
||||
def get_by_ap_id(ap_id) do
|
||||
Repo.one(from object in Object,
|
||||
where: fragment("? @> ?", object.data, ^%{id: ap_id}))
|
||||
where: fragment("(?)->>'id' = ?", object.data, ^ap_id))
|
||||
end
|
||||
|
||||
def get_cached_by_ap_id(ap_id) do
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ defmodule Pleroma.Plugs.AuthenticationPlug do
|
|||
def call(conn, opts) do
|
||||
with {:ok, username, password} <- decode_header(conn),
|
||||
{:ok, user} <- opts[:fetcher].(username),
|
||||
false <- !!user.info["deactivated"],
|
||||
saved_user_id <- get_session(conn, :user_id),
|
||||
{:ok, verified_user} <- verify(user, password, saved_user_id)
|
||||
do
|
||||
|
|
@ -44,7 +45,7 @@ defmodule Pleroma.Plugs.AuthenticationPlug do
|
|||
defp decode_header(conn) do
|
||||
with ["Basic " <> header] <- get_req_header(conn, "authorization"),
|
||||
{:ok, userinfo} <- Base.decode64(header),
|
||||
[username, password] <- String.split(userinfo, ":")
|
||||
[username, password] <- String.split(userinfo, ":", parts: 2)
|
||||
do
|
||||
{:ok, username, password}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,10 +9,15 @@ defmodule Pleroma.Plugs.OAuthPlug do
|
|||
end
|
||||
|
||||
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
|
||||
def call(conn, opts) do
|
||||
with ["Bearer " <> header] <- get_req_header(conn, "authorization"),
|
||||
%Token{user_id: user_id} <- Repo.get_by(Token, token: header),
|
||||
%User{} = user <- Repo.get(User, user_id) do
|
||||
def call(conn, _) do
|
||||
token = case get_req_header(conn, "authorization") do
|
||||
["Bearer " <> header] -> header
|
||||
_ -> get_session(conn, :oauth_token)
|
||||
end
|
||||
with token when not is_nil(token) <- token,
|
||||
%Token{user_id: user_id} <- Repo.get_by(Token, token: token),
|
||||
%User{} = user <- Repo.get(User, user_id),
|
||||
false <- !!user.info["deactivated"] do
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
else
|
||||
|
|
|
|||
|
|
@ -8,11 +8,18 @@ defmodule Pleroma.Upload do
|
|||
result_file = Path.join(upload_folder, file.filename)
|
||||
File.cp!(file.path, result_file)
|
||||
|
||||
# fix content type on some image uploads
|
||||
content_type = if file.content_type == "application/octet-stream" do
|
||||
get_content_type(file.path)
|
||||
else
|
||||
file.content_type
|
||||
end
|
||||
|
||||
%{
|
||||
"type" => "Image",
|
||||
"url" => [%{
|
||||
"type" => "Link",
|
||||
"mediaType" => file.content_type,
|
||||
"mediaType" => content_type,
|
||||
"href" => url_for(Path.join(uuid, :cow_uri.urlencode(file.filename)))
|
||||
}],
|
||||
"name" => file.filename,
|
||||
|
|
@ -53,4 +60,34 @@ defmodule Pleroma.Upload do
|
|||
defp url_for(file) do
|
||||
"#{Web.base_url()}/media/#{file}"
|
||||
end
|
||||
|
||||
def get_content_type(file) do
|
||||
match = File.open(file, [:read], fn(f) ->
|
||||
case IO.binread(f, 8) do
|
||||
<<0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a>> ->
|
||||
"image/png"
|
||||
<<0x47, 0x49, 0x46, 0x38, _, 0x61, _, _>> ->
|
||||
"image/gif"
|
||||
<<0xff, 0xd8, 0xff, _, _, _, _, _>> ->
|
||||
"image/jpeg"
|
||||
<<0x1a, 0x45, 0xdf, 0xa3, _, _, _, _>> ->
|
||||
"video/webm"
|
||||
<<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70>> ->
|
||||
"video/mp4"
|
||||
<<0x49, 0x44, 0x33, _, _, _, _, _>> ->
|
||||
"audio/mpeg"
|
||||
<<0x4f, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00>> ->
|
||||
"audio/ogg"
|
||||
<<0x52, 0x49, 0x46, 0x46, _, _, _, _>> ->
|
||||
"audio/wav"
|
||||
_ ->
|
||||
"application/octet-stream"
|
||||
end
|
||||
end)
|
||||
|
||||
case match do
|
||||
{:ok, type} -> type
|
||||
_e -> "application/octet-stream"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ defmodule Pleroma.User do
|
|||
alias Pleroma.{Repo, User, Object, Web, Activity, Notification}
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Pleroma.Web.{OStatus, Websub}
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.{Utils, ActivityPub}
|
||||
|
||||
schema "users" do
|
||||
field :bio, :string
|
||||
|
|
@ -62,8 +61,9 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def user_info(%User{} = user) do
|
||||
oneself = if user.local, do: 1, else: 0
|
||||
%{
|
||||
following_count: length(user.following),
|
||||
following_count: length(user.following) - oneself,
|
||||
note_count: user.info["note_count"] || 0,
|
||||
follower_count: user.info["follower_count"] || 0
|
||||
}
|
||||
|
|
@ -89,7 +89,7 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def update_changeset(struct, params \\ %{}) do
|
||||
changeset = struct
|
||||
struct
|
||||
|> cast(params, [:bio, :name])
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/)
|
||||
|
|
@ -97,6 +97,25 @@ defmodule Pleroma.User do
|
|||
|> validate_length(:name, min: 1, max: 100)
|
||||
end
|
||||
|
||||
def password_update_changeset(struct, params) do
|
||||
changeset = struct
|
||||
|> cast(params, [:password, :password_confirmation])
|
||||
|> validate_required([:password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|
||||
if changeset.valid? do
|
||||
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
||||
changeset
|
||||
|> put_change(:password_hash, hashed)
|
||||
else
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
def reset_password(user, data) do
|
||||
update_and_set_cache(password_update_changeset(user, data))
|
||||
end
|
||||
|
||||
def register_changeset(struct, params \\ %{}) do
|
||||
changeset = struct
|
||||
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|
||||
|
|
@ -123,9 +142,9 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def follow(%User{} = follower, %User{} = followed) do
|
||||
def follow(%User{} = follower, %User{info: info} = followed) do
|
||||
ap_followers = followed.follower_address
|
||||
if following?(follower, followed) do
|
||||
if following?(follower, followed) or info["deactivated"] do
|
||||
{:error,
|
||||
"Could not follow user: #{followed.nickname} is already on your list."}
|
||||
else
|
||||
|
|
@ -138,9 +157,9 @@ defmodule Pleroma.User do
|
|||
|
||||
follower = follower
|
||||
|> follow_changeset(%{following: following})
|
||||
|> Repo.update
|
||||
|> update_and_set_cache
|
||||
|
||||
{:ok, followed} = update_follower_count(followed)
|
||||
{:ok, _} = update_follower_count(followed)
|
||||
|
||||
follower
|
||||
end
|
||||
|
|
@ -148,13 +167,13 @@ defmodule Pleroma.User do
|
|||
|
||||
def unfollow(%User{} = follower, %User{} = followed) do
|
||||
ap_followers = followed.follower_address
|
||||
if following?(follower, followed) do
|
||||
if following?(follower, followed) and follower.ap_id != followed.ap_id do
|
||||
following = follower.following
|
||||
|> List.delete(ap_followers)
|
||||
|
||||
{ :ok, follower } = follower
|
||||
|> follow_changeset(%{following: following})
|
||||
|> Repo.update
|
||||
|> update_and_set_cache
|
||||
|
||||
{:ok, followed} = update_follower_count(followed)
|
||||
|
||||
|
|
@ -172,6 +191,17 @@ defmodule Pleroma.User do
|
|||
Repo.get_by(User, ap_id: ap_id)
|
||||
end
|
||||
|
||||
def update_and_set_cache(changeset) do
|
||||
with {:ok, user} <- Repo.update(changeset) do
|
||||
Cachex.set(:user_cache, "ap_id:#{user.ap_id}", user)
|
||||
Cachex.set(:user_cache, "nickname:#{user.nickname}", user)
|
||||
Cachex.set(:user_cache, "user_info:#{user.id}", user_info(user))
|
||||
{:ok, user}
|
||||
else
|
||||
e -> e
|
||||
end
|
||||
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)
|
||||
|
|
@ -195,7 +225,7 @@ defmodule Pleroma.User do
|
|||
with %User{} = user <- get_by_nickname(nickname) do
|
||||
user
|
||||
else _e ->
|
||||
with [nick, domain] <- String.split(nickname, "@"),
|
||||
with [_nick, _domain] <- String.split(nickname, "@"),
|
||||
{:ok, user} <- OStatus.make_user(nickname) do
|
||||
user
|
||||
else _e -> nil
|
||||
|
|
@ -220,9 +250,18 @@ defmodule Pleroma.User do
|
|||
{:ok, Repo.all(q)}
|
||||
end
|
||||
|
||||
def increase_note_count(%User{} = user) do
|
||||
note_count = (user.info["note_count"] || 0) + 1
|
||||
new_info = Map.put(user.info, "note_count", note_count)
|
||||
|
||||
cs = info_changeset(user, %{info: new_info})
|
||||
|
||||
update_and_set_cache(cs)
|
||||
end
|
||||
|
||||
def update_note_count(%User{} = user) do
|
||||
note_count_query = from a in Object,
|
||||
where: fragment("? @> ?", a.data, ^%{actor: user.ap_id, type: "Note"}),
|
||||
where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
|
||||
select: count(a.id)
|
||||
|
||||
note_count = Repo.one(note_count_query)
|
||||
|
|
@ -231,12 +270,13 @@ defmodule Pleroma.User do
|
|||
|
||||
cs = info_changeset(user, %{info: new_info})
|
||||
|
||||
Repo.update(cs)
|
||||
update_and_set_cache(cs)
|
||||
end
|
||||
|
||||
def update_follower_count(%User{} = user) do
|
||||
follower_count_query = from u in User,
|
||||
where: fragment("? @> ?", u.following, ^user.follower_address),
|
||||
where: u.id != ^user.id,
|
||||
select: count(u.id)
|
||||
|
||||
follower_count = Repo.one(follower_count_query)
|
||||
|
|
@ -245,14 +285,95 @@ defmodule Pleroma.User do
|
|||
|
||||
cs = info_changeset(user, %{info: new_info})
|
||||
|
||||
Repo.update(cs)
|
||||
update_and_set_cache(cs)
|
||||
end
|
||||
|
||||
def get_notified_from_activity(%Activity{data: %{"to" => to}} = activity) do
|
||||
def get_notified_from_activity(%Activity{data: %{"to" => to}}) do
|
||||
query = from u in User,
|
||||
where: u.ap_id in ^to,
|
||||
where: u.local == true
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
def get_recipients_from_activity(%Activity{data: %{"to" => to}}) do
|
||||
query = from u in User,
|
||||
where: u.ap_id in ^to,
|
||||
or_where: fragment("? \\\?| ?", u.following, ^to)
|
||||
|
||||
query = from u in query,
|
||||
where: u.local == true
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
def search(query, resolve) do
|
||||
if resolve do
|
||||
User.get_or_fetch_by_nickname(query)
|
||||
end
|
||||
q = from u in User,
|
||||
where: fragment("(to_tsvector('english', ?) || to_tsvector('english', ?)) @@ plainto_tsquery('english', ?)", u.nickname, u.name, ^query),
|
||||
limit: 20
|
||||
Repo.all(q)
|
||||
end
|
||||
|
||||
def block(user, %{ap_id: ap_id}) do
|
||||
blocks = user.info["blocks"] || []
|
||||
new_blocks = Enum.uniq([ap_id | blocks])
|
||||
new_info = Map.put(user.info, "blocks", new_blocks)
|
||||
|
||||
cs = User.info_changeset(user, %{info: new_info})
|
||||
update_and_set_cache(cs)
|
||||
end
|
||||
|
||||
def unblock(user, %{ap_id: ap_id}) do
|
||||
blocks = user.info["blocks"] || []
|
||||
new_blocks = List.delete(blocks, ap_id)
|
||||
new_info = Map.put(user.info, "blocks", new_blocks)
|
||||
|
||||
cs = User.info_changeset(user, %{info: new_info})
|
||||
update_and_set_cache(cs)
|
||||
end
|
||||
|
||||
def blocks?(user, %{ap_id: ap_id}) do
|
||||
blocks = user.info["blocks"] || []
|
||||
Enum.member?(blocks, ap_id)
|
||||
end
|
||||
|
||||
def local_user_query() do
|
||||
from u in User,
|
||||
where: u.local == true
|
||||
end
|
||||
|
||||
def deactivate (%User{} = user) do
|
||||
new_info = Map.put(user.info, "deactivated", true)
|
||||
cs = User.info_changeset(user, %{info: new_info})
|
||||
update_and_set_cache(cs)
|
||||
end
|
||||
|
||||
def delete (%User{} = user) do
|
||||
{:ok, user} = User.deactivate(user)
|
||||
|
||||
# Remove all relationships
|
||||
{:ok, followers } = User.get_followers(user)
|
||||
followers
|
||||
|> Enum.each(fn (follower) -> User.unfollow(follower, user) end)
|
||||
|
||||
{:ok, friends} = User.get_friends(user)
|
||||
friends
|
||||
|> Enum.each(fn (followed) -> User.unfollow(user, followed) end)
|
||||
|
||||
query = from a in Activity,
|
||||
where: a.actor == ^user.ap_id
|
||||
|
||||
Repo.all(query)
|
||||
|> Enum.each(fn (activity) ->
|
||||
case activity.data["type"] do
|
||||
"Create" -> ActivityPub.delete(Object.get_by_ap_id(activity.data["object"]["id"]))
|
||||
_ -> "Doing nothing" # TODO: Do something with likes, follows, repeats.
|
||||
end
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
alias Pleroma.{Activity, Repo, Object, Upload, User, Web, Notification}
|
||||
alias Ecto.{Changeset, UUID}
|
||||
alias Pleroma.{Activity, Repo, Object, Upload, User, Notification}
|
||||
import Ecto.Query
|
||||
import Pleroma.Web.ActivityPub.Utils
|
||||
require Logger
|
||||
|
|
@ -9,8 +8,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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})
|
||||
{:ok, activity} = Repo.insert(%Activity{data: map, local: local, actor: map["actor"]})
|
||||
Notification.create_notifications(activity)
|
||||
stream_out(activity)
|
||||
{:ok, activity}
|
||||
else
|
||||
%Activity{} = activity -> {:ok, activity}
|
||||
|
|
@ -18,6 +18,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
end
|
||||
|
||||
def stream_out(activity) do
|
||||
if activity.data["type"] in ["Create", "Announce"] do
|
||||
Pleroma.Web.Streamer.stream("user", activity)
|
||||
if Enum.member?(activity.data["to"], "https://www.w3.org/ns/activitystreams#Public") do
|
||||
Pleroma.Web.Streamer.stream("public", activity)
|
||||
if activity.local do
|
||||
Pleroma.Web.Streamer.stream("public:local", activity)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create(to, actor, context, object, additional \\ %{}, published \\ nil, local \\ true) do
|
||||
with create_data <- make_create_data(%{to: to, actor: actor, published: published, context: context, object: object}, additional),
|
||||
{:ok, activity} <- insert(create_data, local),
|
||||
|
|
@ -27,7 +39,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
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" => id}} = object, activity_id \\ nil, local \\ true) do
|
||||
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),
|
||||
like_data <- make_like_data(user, object, activity_id),
|
||||
{:ok, activity} <- insert(like_data, local),
|
||||
|
|
@ -49,7 +61,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
end
|
||||
|
||||
def announce(%User{ap_id: ap_id} = user, %Object{data: %{"id" => id}} = object, activity_id \\ nil, local \\ true) do
|
||||
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),
|
||||
{:ok, activity} <- insert(announce_data, local),
|
||||
{:ok, object} <- add_announce_to_object(activity, object),
|
||||
|
|
@ -87,17 +99,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
}
|
||||
with Repo.delete(object),
|
||||
Repo.delete_all(Activity.all_non_create_by_object_ap_id_q(id)),
|
||||
Repo.delete_all(Activity.all_by_object_ap_id_q(id)),
|
||||
{:ok, activity} <- insert(data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_activities_for_context(context) do
|
||||
def fetch_activities_for_context(context, opts \\ %{}) do
|
||||
query = from activity in Activity,
|
||||
where: fragment("?->>'type' = ? and ?->>'context' = ?", activity.data, "Create", activity.data, ^context),
|
||||
order_by: [desc: :id]
|
||||
query = restrict_blocked(query, opts)
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
|
|
@ -137,7 +149,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
defp restrict_actor(query, %{"actor_id" => actor_id}) do
|
||||
from activity in query,
|
||||
where: fragment("?->>'actor' = ?", activity.data, ^actor_id)
|
||||
where: activity.actor == ^actor_id
|
||||
end
|
||||
defp restrict_actor(query, _), do: query
|
||||
|
||||
|
|
@ -156,10 +168,32 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
defp restrict_favorited_by(query, _), do: query
|
||||
|
||||
defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
|
||||
from activity in query,
|
||||
where: fragment("not (? #> '{\"object\",\"attachment\"}' = ?)", activity.data, ^[])
|
||||
end
|
||||
defp restrict_media(query, _), do: query
|
||||
|
||||
# Only search through last 100_000 activities by default
|
||||
defp restrict_recent(query, %{"whole_db" => true}), do: query
|
||||
defp restrict_recent(query, _) do
|
||||
since = (Repo.aggregate(Activity, :max, :id) || 0) - 100_000
|
||||
|
||||
from activity in query,
|
||||
where: activity.id > ^since
|
||||
end
|
||||
|
||||
defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
|
||||
blocks = info["blocks"] || []
|
||||
from activity in query,
|
||||
where: fragment("not (? = ANY(?))", activity.actor, ^blocks)
|
||||
end
|
||||
defp restrict_blocked(query, _), do: query
|
||||
|
||||
def fetch_activities(recipients, opts \\ %{}) do
|
||||
base_query = from activity in Activity,
|
||||
limit: 20,
|
||||
order_by: [desc: :id]
|
||||
order_by: [fragment("? desc nulls last", activity.id)]
|
||||
|
||||
base_query
|
||||
|> restrict_recipients(recipients)
|
||||
|
|
@ -170,6 +204,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> restrict_actor(opts)
|
||||
|> restrict_type(opts)
|
||||
|> restrict_favorited_by(opts)
|
||||
|> restrict_recent(opts)
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_media(opts)
|
||||
|> Repo.all
|
||||
|> Enum.reverse
|
||||
end
|
||||
|
|
|
|||
|
|
@ -29,7 +29,12 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
Enqueues an activity for federation if it's local
|
||||
"""
|
||||
def maybe_federate(%Activity{local: true} = activity) do
|
||||
Pleroma.Web.Federator.enqueue(:publish, activity)
|
||||
priority = case activity.data["type"] do
|
||||
"Delete" -> 10
|
||||
"Create" -> 1
|
||||
_ -> 5
|
||||
end
|
||||
Pleroma.Web.Federator.enqueue(:publish, activity, priority)
|
||||
:ok
|
||||
end
|
||||
def maybe_federate(_), do: :ok
|
||||
|
|
@ -64,7 +69,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
Inserts a full object if it is contained in an activity.
|
||||
"""
|
||||
def insert_full_object(%{"object" => object_data}) when is_map(object_data) do
|
||||
with {:ok, object} <- Object.create(object_data) do
|
||||
with {:ok, _} <- Object.create(object_data) do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
|
@ -88,9 +93,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
@doc """
|
||||
Returns an existing like if a user already liked an object
|
||||
"""
|
||||
def get_existing_like(actor, %{data: %{"id" => id}} = object) do
|
||||
def get_existing_like(actor, %{data: %{"id" => id}}) do
|
||||
query = from activity in Activity,
|
||||
where: fragment("? @> ?", activity.data, ^%{actor: actor, object: id, type: "Like"})
|
||||
where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
|
||||
# this is to use the index
|
||||
where: fragment("coalesce((?)->'object'->>'id', (?)->>'object') = ?", activity.data, activity.data, ^id),
|
||||
where: fragment("(?)->>'type' = 'Like'", activity.data)
|
||||
|
||||
Repo.one(query)
|
||||
end
|
||||
|
||||
|
|
@ -197,7 +206,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
def make_create_data(params, additional) do
|
||||
published = params.published || make_date()
|
||||
|
||||
activity = %{
|
||||
%{
|
||||
"type" => "Create",
|
||||
"to" => params.to |> Enum.uniq,
|
||||
"actor" => params.actor.ap_id,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
defmodule Pleroma.Web.UserSocket do
|
||||
use Phoenix.Socket
|
||||
alias Pleroma.User
|
||||
alias Comeonin.Pbkdf2
|
||||
|
||||
## Channels
|
||||
# channel "room:*", Pleroma.Web.RoomChannel
|
||||
channel "chat:*", Pleroma.Web.ChatChannel
|
||||
|
||||
## Transports
|
||||
transport :websocket, Phoenix.Transports.WebSocket
|
||||
|
|
@ -19,8 +22,13 @@ defmodule Pleroma.Web.UserSocket do
|
|||
#
|
||||
# See `Phoenix.Token` documentation for examples in
|
||||
# performing token verification on connect.
|
||||
def connect(_params, socket) do
|
||||
{:ok, socket}
|
||||
def connect(%{"token" => token}, socket) do
|
||||
with {:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84600),
|
||||
%User{} = user <- Pleroma.Repo.get(User, user_id) do
|
||||
{:ok, assign(socket, :user_name, user.nickname)}
|
||||
else
|
||||
_e -> :error
|
||||
end
|
||||
end
|
||||
|
||||
# Socket id's are topics that allow you to identify all sockets for a given user:
|
||||
|
|
|
|||
46
lib/pleroma/web/chat_channel.ex
Normal file
46
lib/pleroma/web/chat_channel.ex
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
defmodule Pleroma.Web.ChatChannel do
|
||||
use Phoenix.Channel
|
||||
alias Pleroma.Web.ChatChannel.ChatChannelState
|
||||
alias Pleroma.User
|
||||
|
||||
def join("chat:public", _message, socket) do
|
||||
send(self(), :after_join)
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
def handle_info(:after_join, socket) do
|
||||
push socket, "messages", %{messages: ChatChannelState.messages()}
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} = socket) do
|
||||
author = User.get_cached_by_nickname(user_name)
|
||||
author = Pleroma.Web.MastodonAPI.AccountView.render("account.json", user: author)
|
||||
message = ChatChannelState.add_message(%{text: text, author: author})
|
||||
|
||||
broadcast! socket, "new_msg", message
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Pleroma.Web.ChatChannel.ChatChannelState do
|
||||
use Agent
|
||||
@max_messages 20
|
||||
|
||||
def start_link do
|
||||
Agent.start_link(fn -> %{max_id: 1, messages: []} end, name: __MODULE__)
|
||||
end
|
||||
|
||||
def add_message(message) do
|
||||
Agent.get_and_update(__MODULE__, fn state ->
|
||||
id = state[:max_id] + 1
|
||||
message = Map.put(message, "id", id)
|
||||
messages = [message | state[:messages]] |> Enum.take(@max_messages)
|
||||
{message, %{max_id: id, messages: messages}}
|
||||
end)
|
||||
end
|
||||
|
||||
def messages() do
|
||||
Agent.get(__MODULE__, fn state -> state[:messages] |> Enum.reverse end)
|
||||
end
|
||||
end
|
||||
|
|
@ -16,7 +16,6 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
|
||||
def repeat(id_or_ap_id, user) do
|
||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
false <- activity.data["actor"] == user.ap_id,
|
||||
object <- Object.get_by_ap_id(activity.data["object"]["id"]) do
|
||||
ActivityPub.announce(user, object)
|
||||
else
|
||||
|
|
@ -56,12 +55,14 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
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),
|
||||
tags <- Formatter.parse_tags(status),
|
||||
content_html <- make_content_html(status, mentions, attachments, tags),
|
||||
tags <- Formatter.parse_tags(status, data),
|
||||
content_html <- make_content_html(status, mentions, attachments, tags, data["no_attachment_links"]),
|
||||
context <- make_context(inReplyTo),
|
||||
object <- make_note_data(user.ap_id, to, context, content_html, attachments, inReplyTo, tags) do
|
||||
cw <- data["spoiler_text"],
|
||||
object <- make_note_data(user.ap_id, to, context, content_html, attachments, inReplyTo, tags, cw),
|
||||
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)
|
||||
User.update_note_count(user)
|
||||
User.increase_note_count(user)
|
||||
res
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -38,15 +38,19 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
end
|
||||
end
|
||||
|
||||
def make_content_html(status, mentions, attachments, tags) do
|
||||
def make_content_html(status, mentions, attachments, tags, no_attachment_links \\ false) do
|
||||
status
|
||||
|> format_input(mentions, tags)
|
||||
|> add_attachments(attachments)
|
||||
|> maybe_add_attachments(attachments, no_attachment_links)
|
||||
end
|
||||
|
||||
def make_context(%Activity{data: %{"context" => context}}), do: context
|
||||
def make_context(_), do: Utils.generate_context_id
|
||||
|
||||
def maybe_add_attachments(text, attachments, _no_links = true), do: text
|
||||
def maybe_add_attachments(text, attachments, _no_links) do
|
||||
add_attachments(text, attachments)
|
||||
end
|
||||
def add_attachments(text, attachments) do
|
||||
attachment_text = Enum.map(attachments, fn
|
||||
(%{"url" => [%{"href" => href} | _]}) ->
|
||||
|
|
@ -54,15 +58,16 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
"<a href=\"#{href}\" class='attachment'>#{shortname(name)}</a>"
|
||||
_ -> ""
|
||||
end)
|
||||
Enum.join([text | attachment_text], "<br>\n")
|
||||
Enum.join([text | attachment_text], "<br>")
|
||||
end
|
||||
|
||||
def format_input(text, mentions, tags) do
|
||||
HtmlSanitizeEx.strip_tags(text)
|
||||
def format_input(text, mentions, _tags) do
|
||||
text
|
||||
|> Formatter.html_escape
|
||||
|> Formatter.linkify
|
||||
|> String.replace("\n", "<br>\n")
|
||||
|> String.replace("\n", "<br>")
|
||||
|> add_user_links(mentions)
|
||||
|> add_tag_links(tags)
|
||||
# |> add_tag_links(tags)
|
||||
end
|
||||
|
||||
def add_tag_links(text, tags) do
|
||||
|
|
@ -94,11 +99,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
end)
|
||||
end
|
||||
|
||||
def make_note_data(actor, to, context, content_html, attachments, inReplyTo, tags) do
|
||||
def make_note_data(actor, to, context, content_html, attachments, inReplyTo, tags, cw \\ nil) do
|
||||
object = %{
|
||||
"type" => "Note",
|
||||
"to" => to,
|
||||
"content" => content_html,
|
||||
"summary" => cw,
|
||||
"context" => context,
|
||||
"attachment" => attachments,
|
||||
"actor" => actor,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ defmodule Pleroma.Web.Endpoint do
|
|||
use Phoenix.Endpoint, otp_app: :pleroma
|
||||
|
||||
socket "/socket", Pleroma.Web.UserSocket
|
||||
socket "/api/v1", Pleroma.Web.MastodonAPI.MastodonSocket
|
||||
|
||||
# Serve at "/" the static files from "priv/static" directory.
|
||||
#
|
||||
|
|
@ -11,7 +12,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)
|
||||
only: ~w(index.html static finmoji emoji packs sounds sw.js)
|
||||
|
||||
# Code reloading can be explicitly enabled under the
|
||||
# :code_reloader configuration of your endpoint.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ defmodule Pleroma.Web.Federator do
|
|||
Process.sleep(1000 * 60 * 1) # 1 minute
|
||||
enqueue(:refresh_subscriptions, nil)
|
||||
end)
|
||||
GenServer.start_link(__MODULE__, {:sets.new(), :queue.new()}, name: __MODULE__)
|
||||
GenServer.start_link(__MODULE__, %{
|
||||
in: {:sets.new(), []},
|
||||
out: {:sets.new(), []}
|
||||
}, name: __MODULE__)
|
||||
end
|
||||
|
||||
def handle(:refresh_subscriptions, _) do
|
||||
|
|
@ -71,22 +74,22 @@ defmodule Pleroma.Web.Federator do
|
|||
end
|
||||
end
|
||||
|
||||
def handle(type, payload) do
|
||||
def handle(type, _) do
|
||||
Logger.debug(fn -> "Unknown task: #{type}" end)
|
||||
{:error, "Don't know what do do with this"}
|
||||
end
|
||||
|
||||
def enqueue(type, payload) do
|
||||
def enqueue(type, payload, priority \\ 1) do
|
||||
if Mix.env == :test do
|
||||
handle(type, payload)
|
||||
else
|
||||
GenServer.cast(__MODULE__, {:enqueue, type, payload})
|
||||
GenServer.cast(__MODULE__, {:enqueue, type, payload, priority})
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_start_job(running_jobs, queue) do
|
||||
if (:sets.size(running_jobs) < @max_jobs) && !:queue.is_empty(queue) do
|
||||
{{:value, {type, payload}}, queue} = :queue.out(queue)
|
||||
if (:sets.size(running_jobs) < @max_jobs) && queue != [] do
|
||||
{{type, payload}, queue} = queue_pop(queue)
|
||||
{:ok, pid} = Task.start(fn -> handle(type, payload) end)
|
||||
mref = Process.monitor(pid)
|
||||
{:sets.add_element(mref, running_jobs), queue}
|
||||
|
|
@ -95,20 +98,41 @@ defmodule Pleroma.Web.Federator do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_cast({:enqueue, type, payload}, {running_jobs, queue}) do
|
||||
queue = :queue.in({type, payload}, queue)
|
||||
{running_jobs, queue} = maybe_start_job(running_jobs, queue)
|
||||
{:noreply, {running_jobs, queue}}
|
||||
def handle_cast({:enqueue, type, payload, priority}, state) when type in [:incoming_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)
|
||||
{:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}}
|
||||
end
|
||||
|
||||
def handle_info({:DOWN, ref, :process, _pid, _reason}, {running_jobs, queue}) do
|
||||
running_jobs = :sets.del_element(ref, running_jobs)
|
||||
{running_jobs, queue} = maybe_start_job(running_jobs, queue)
|
||||
{:noreply, {running_jobs, queue}}
|
||||
def handle_cast({:enqueue, type, payload, priority}, state) do
|
||||
%{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state
|
||||
o_queue = enqueue_sorted(o_queue, {type, payload}, 1)
|
||||
{o_running_jobs, o_queue} = maybe_start_job(o_running_jobs, o_queue)
|
||||
{:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}}
|
||||
end
|
||||
|
||||
def handle_cast(m, state) do
|
||||
IO.inspect("Unknown: #{inspect(m)}, #{inspect(state)}")
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do
|
||||
%{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state
|
||||
i_running_jobs = :sets.del_element(ref, i_running_jobs)
|
||||
o_running_jobs = :sets.del_element(ref, o_running_jobs)
|
||||
{i_running_jobs, i_queue} = maybe_start_job(i_running_jobs, i_queue)
|
||||
{o_running_jobs, o_queue} = maybe_start_job(o_running_jobs, o_queue)
|
||||
|
||||
{:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}}
|
||||
end
|
||||
|
||||
def enqueue_sorted(queue, element, priority) do
|
||||
[%{item: element, priority: priority} | queue]
|
||||
|> Enum.sort_by(fn (%{priority: priority}) -> priority end)
|
||||
end
|
||||
|
||||
def queue_pop([%{item: element} | queue]) do
|
||||
{element, queue}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
use Pleroma.Web, :controller
|
||||
alias Pleroma.{Repo, Activity, User, Notification}
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.MastodonAPI.{StatusView, AccountView}
|
||||
alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView}
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.{CommonAPI, OStatus}
|
||||
alias Pleroma.Web.OAuth.{Authorization, Token, App}
|
||||
alias Comeonin.Pbkdf2
|
||||
import Ecto.Query
|
||||
import Logger
|
||||
require Logger
|
||||
|
||||
def create_app(conn, params) do
|
||||
with cs <- App.register_changeset(%App{}, params) |> IO.inspect,
|
||||
|
|
@ -23,7 +23,58 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def verify_credentials(%{assigns: %{user: user}} = conn, params) do
|
||||
def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
||||
params = if bio = params["note"] do
|
||||
Map.put(params, "bio", bio)
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
params = if name = params["display_name"] do
|
||||
Map.put(params, "name", name)
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
user = if avatar = params["avatar"] do
|
||||
with %Plug.Upload{} <- avatar,
|
||||
{:ok, object} <- ActivityPub.upload(avatar),
|
||||
change = Ecto.Changeset.change(user, %{avatar: object.data}),
|
||||
{:ok, user} = Repo.update(change) do
|
||||
user
|
||||
else
|
||||
_e -> user
|
||||
end
|
||||
else
|
||||
user
|
||||
end
|
||||
|
||||
user = if banner = params["header"] do
|
||||
with %Plug.Upload{} <- banner,
|
||||
{: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
|
||||
user
|
||||
else
|
||||
_e -> user
|
||||
end
|
||||
else
|
||||
user
|
||||
end
|
||||
|
||||
with changeset <- User.update_changeset(user, params),
|
||||
{:ok, user} <- Repo.update(changeset) do
|
||||
json conn, AccountView.render("account.json", %{user: user})
|
||||
else
|
||||
_e ->
|
||||
conn
|
||||
|> put_status(403)
|
||||
|> json(%{error: "Invalid request"})
|
||||
end
|
||||
end
|
||||
|
||||
def verify_credentials(%{assigns: %{user: user}} = conn, _) do
|
||||
account = AccountView.render("account.json", %{user: user})
|
||||
json(conn, account)
|
||||
end
|
||||
|
|
@ -42,6 +93,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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),
|
||||
|
|
@ -52,15 +104,33 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
streaming_api: String.replace(Web.base_url, ["http","https"], "wss")
|
||||
},
|
||||
stats: %{
|
||||
user_count: 1,
|
||||
status_count: 2,
|
||||
user_count: user_count,
|
||||
domain_count: 3
|
||||
}
|
||||
},
|
||||
max_toot_chars: Keyword.get(@instance, :limit)
|
||||
}
|
||||
|
||||
json(conn, response)
|
||||
end
|
||||
|
||||
defp mastodonized_emoji do
|
||||
Pleroma.Formatter.get_custom_emoji()
|
||||
|> Enum.map(fn {shortcode, relative_url} ->
|
||||
url = to_string URI.merge(Web.base_url(), relative_url)
|
||||
%{
|
||||
"shortcode" => shortcode,
|
||||
"static_url" => url,
|
||||
"url" => url
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
def custom_emojis(conn, _params) do
|
||||
mastodon_emoji = mastodonized_emoji()
|
||||
json conn, mastodon_emoji
|
||||
end
|
||||
|
||||
defp add_link_headers(conn, method, activities) do
|
||||
last = List.last(activities)
|
||||
first = List.first(activities)
|
||||
|
|
@ -79,6 +149,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
def home_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||
params = params
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|
||||
activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||
|> Enum.reverse
|
||||
|
|
@ -92,6 +163,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
params = params
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("local_only", !!params["local"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|
||||
activities = ActivityPub.fetch_public_activities(params)
|
||||
|> Enum.reverse
|
||||
|
|
@ -107,6 +179,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
params = params
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("actor_id", ap_id)
|
||||
|> Map.put("whole_db", true)
|
||||
|
||||
activities = ActivityPub.fetch_activities([], params)
|
||||
|> Enum.reverse
|
||||
|
|
@ -123,8 +196,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|
||||
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"]),
|
||||
activities <- ActivityPub.fetch_activities_for_context(activity.data["object"]["context"], %{"blocking_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
|
||||
result = %{
|
||||
ancestors: StatusView.render("index.json", for: user, activities: grouped_activities[true] || [], as: :activity) |> Enum.reverse,
|
||||
|
|
@ -135,9 +209,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def post_status(%{assigns: %{user: user}} = conn, %{"status" => status} = params) do
|
||||
def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
|
||||
params = params
|
||||
|> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
|
||||
|> Map.put("no_attachment_links", true)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, params)
|
||||
render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}
|
||||
|
|
@ -155,9 +230,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||
with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(ap_id_or_id, user),
|
||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||
render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}
|
||||
with {:ok, announce, _activity} = CommonAPI.repeat(ap_id_or_id, user) do
|
||||
render conn, StatusView, "status.json", %{activity: announce, for: user, as: :activity}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -177,23 +251,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|
||||
def notifications(%{assigns: %{user: user}} = conn, params) do
|
||||
notifications = Notification.for_user(user, params)
|
||||
result = Enum.map(notifications, fn (%{id: id, activity: activity, inserted_at: created_at}) ->
|
||||
actor = User.get_cached_by_ap_id(activity.data["actor"])
|
||||
created_at = NaiveDateTime.to_iso8601(created_at)
|
||||
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
|
||||
case activity.data["type"] do
|
||||
"Create" ->
|
||||
%{id: id, type: "mention", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: activity})}
|
||||
"Like" ->
|
||||
liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
|
||||
%{id: id, type: "favourite", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: liked_activity})}
|
||||
"Announce" ->
|
||||
announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
|
||||
%{id: id, type: "reblog", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: announced_activity})}
|
||||
"Follow" ->
|
||||
%{id: id, type: "follow", created_at: created_at, account: AccountView.render("account.json", %{user: actor})}
|
||||
_ -> nil
|
||||
end
|
||||
result = Enum.map(notifications, fn x ->
|
||||
render_notification(user, x)
|
||||
end)
|
||||
|> Enum.filter(&(&1))
|
||||
|
||||
|
|
@ -202,6 +261,33 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|> json(result)
|
||||
end
|
||||
|
||||
def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
|
||||
with {:ok, notification} <- Notification.get(user, id) do
|
||||
json(conn, render_notification(user, notification))
|
||||
else
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(403, Poison.encode!(%{"error" => reason}))
|
||||
end
|
||||
end
|
||||
|
||||
def clear_notifications(%{assigns: %{user: user}} = conn, _params) do
|
||||
Notification.clear(user)
|
||||
json(conn, %{})
|
||||
end
|
||||
|
||||
def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
|
||||
with {:ok, _notif} <- Notification.dismiss(user, id) do
|
||||
json(conn, %{})
|
||||
else
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(403, Poison.encode!(%{"error" => reason}))
|
||||
end
|
||||
end
|
||||
|
||||
def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
id = List.wrap(id)
|
||||
q = from u in User,
|
||||
|
|
@ -210,7 +296,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
render conn, AccountView, "relationships.json", %{user: user, targets: targets}
|
||||
end
|
||||
|
||||
def upload(%{assigns: %{user: user}} = conn, %{"file" => file}) do
|
||||
def upload(%{assigns: %{user: _}} = conn, %{"file" => file}) do
|
||||
with {:ok, object} <- ActivityPub.upload(file) do
|
||||
data = object.data
|
||||
|> Map.put("id", object.id)
|
||||
|
|
@ -220,7 +306,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def favourited_by(conn, %{"id" => id}) do
|
||||
with %Activity{data: %{"object" => %{"likes" => likes} = data}} <- Repo.get(Activity, id) do
|
||||
with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do
|
||||
q = from u in User,
|
||||
where: u.ap_id in ^likes
|
||||
users = Repo.all(q)
|
||||
|
|
@ -246,6 +332,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
params = params
|
||||
|> Map.put("type", "Create")
|
||||
|> Map.put("local_only", !!params["local"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|
||||
activities = ActivityPub.fetch_public_activities(params)
|
||||
|> Enum.reverse
|
||||
|
|
@ -271,9 +358,27 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|
||||
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||
with %User{} = followed <- Repo.get(User, id),
|
||||
{:ok, follower} <- User.follow(follower, followed),
|
||||
{:ok, activity} <- ActivityPub.follow(follower, followed) do
|
||||
{:ok, follower} <- User.follow(follower, followed),
|
||||
{:ok, _activity} <- ActivityPub.follow(follower, followed) do
|
||||
render conn, AccountView, "relationship.json", %{user: follower, target: followed}
|
||||
else
|
||||
{:error, message} ->
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(403, Poison.encode!(%{"error" => message}))
|
||||
end
|
||||
end
|
||||
|
||||
def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
|
||||
with %User{} = followed <- Repo.get_by(User, nickname: uri),
|
||||
{:ok, follower} <- User.follow(follower, followed),
|
||||
{:ok, _activity} <- ActivityPub.follow(follower, followed) do
|
||||
render conn, AccountView, "account.json", %{user: followed}
|
||||
else
|
||||
{:error, message} ->
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(403, Poison.encode!(%{"error" => message}))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -290,21 +395,55 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
if params["resolve"] == "true" do
|
||||
User.get_or_fetch_by_nickname(query)
|
||||
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||
with %User{} = blocked <- Repo.get(User, id),
|
||||
{:ok, blocker} <- User.block(blocker, blocked) do
|
||||
render conn, AccountView, "relationship.json", %{user: blocker, target: blocked}
|
||||
else
|
||||
{:error, message} ->
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(403, Poison.encode!(%{"error" => message}))
|
||||
end
|
||||
end
|
||||
|
||||
q = from u in User,
|
||||
where: fragment("(to_tsvector('english', ?) || to_tsvector('english', ?)) @@ plainto_tsquery('english', ?)", u.nickname, u.name, ^query),
|
||||
limit: 20
|
||||
accounts = Repo.all(q)
|
||||
def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||
with %User{} = blocked <- Repo.get(User, id),
|
||||
{:ok, blocker} <- User.unblock(blocker, blocked) do
|
||||
render conn, AccountView, "relationship.json", %{user: blocker, target: blocked}
|
||||
else
|
||||
{:error, message} ->
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(403, Poison.encode!(%{"error" => message}))
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Use proper query
|
||||
def blocks(%{assigns: %{user: user}} = conn, _) do
|
||||
with blocked_users <- user.info["blocks"] || [],
|
||||
accounts <- Enum.map(blocked_users, fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end) do
|
||||
res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
|
||||
json(conn, res)
|
||||
end
|
||||
end
|
||||
|
||||
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
accounts = User.search(query, params["resolve"] == "true")
|
||||
|
||||
fetched = if Regex.match?(~r/https?:/, query) do
|
||||
with {:ok, activities} <- OStatus.fetch_activity_from_url(query) do
|
||||
activities
|
||||
else
|
||||
_e -> []
|
||||
end
|
||||
end || []
|
||||
|
||||
q = from a in Activity,
|
||||
where: fragment("?->>'type' = 'Create'", a.data),
|
||||
where: fragment("to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)", a.data, ^query),
|
||||
limit: 20
|
||||
statuses = Repo.all(q)
|
||||
statuses = Repo.all(q) ++ fetched
|
||||
|
||||
res = %{
|
||||
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
|
||||
|
|
@ -315,10 +454,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
json(conn, res)
|
||||
end
|
||||
|
||||
def favourites(%{assigns: %{user: user}} = conn, params) do
|
||||
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
accounts = User.search(query, params["resolve"] == "true")
|
||||
|
||||
res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
|
||||
|
||||
json(conn, res)
|
||||
end
|
||||
|
||||
def favourites(%{assigns: %{user: user}} = conn, _) do
|
||||
params = conn
|
||||
|> Map.put("type", "Create")
|
||||
|> Map.put("favorited_by", user.ap_id)
|
||||
|> Map.put("blocking_user", user)
|
||||
|
||||
activities = ActivityPub.fetch_activities([], params)
|
||||
|> Enum.reverse
|
||||
|
|
@ -327,6 +475,127 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
|
||||
end
|
||||
|
||||
def index(%{assigns: %{user: user}} = conn, _params) do
|
||||
token = conn
|
||||
|> get_session(:oauth_token)
|
||||
|
||||
if user && token do
|
||||
mastodon_emoji = mastodonized_emoji()
|
||||
accounts = Map.put(%{}, user.id, AccountView.render("account.json", %{user: user}))
|
||||
initial_state = %{
|
||||
meta: %{
|
||||
streaming_api_base_url: String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
|
||||
access_token: token,
|
||||
locale: "en",
|
||||
domain: Pleroma.Web.Endpoint.host(),
|
||||
admin: "1",
|
||||
me: "#{user.id}",
|
||||
unfollow_modal: false,
|
||||
boost_modal: false,
|
||||
delete_modal: true,
|
||||
auto_play_gif: false,
|
||||
reduce_motion: false
|
||||
},
|
||||
compose: %{
|
||||
me: "#{user.id}",
|
||||
default_privacy: "public",
|
||||
default_sensitive: false
|
||||
},
|
||||
media_attachments: %{
|
||||
accept_content_types: [
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".png",
|
||||
".gif",
|
||||
".webm",
|
||||
".mp4",
|
||||
".m4v",
|
||||
"image\/jpeg",
|
||||
"image\/png",
|
||||
"image\/gif",
|
||||
"video\/webm",
|
||||
"video\/mp4"
|
||||
]
|
||||
},
|
||||
settings: %{
|
||||
onboarded: true,
|
||||
home: %{
|
||||
shows: %{
|
||||
reblog: true,
|
||||
reply: true
|
||||
}
|
||||
},
|
||||
notifications: %{
|
||||
alerts: %{
|
||||
follow: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
mention: true
|
||||
},
|
||||
shows: %{
|
||||
follow: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
mention: true
|
||||
},
|
||||
sounds: %{
|
||||
follow: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
mention: true
|
||||
}
|
||||
}
|
||||
},
|
||||
push_subscription: nil,
|
||||
accounts: accounts,
|
||||
custom_emojis: mastodon_emoji
|
||||
} |> Poison.encode!
|
||||
conn
|
||||
|> put_layout(false)
|
||||
|> render(MastodonView, "index.html", %{initial_state: initial_state})
|
||||
else
|
||||
conn
|
||||
|> redirect(to: "/web/login")
|
||||
end
|
||||
end
|
||||
|
||||
def login(conn, _) do
|
||||
conn
|
||||
|> render(MastodonView, "login.html", %{error: false})
|
||||
end
|
||||
|
||||
defp get_or_make_app() do
|
||||
with %App{} = app <- Repo.get_by(App, client_name: "Mastodon-Local") do
|
||||
{:ok, app}
|
||||
else
|
||||
_e ->
|
||||
cs = App.register_changeset(%App{}, %{client_name: "Mastodon-Local", redirect_uris: ".", scopes: "read,write,follow"})
|
||||
Repo.insert(cs)
|
||||
end
|
||||
end
|
||||
|
||||
def login_post(conn, %{"authorization" => %{ "name" => name, "password" => password}}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(name),
|
||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||
{:ok, app} <- get_or_make_app(),
|
||||
{:ok, auth} <- Authorization.create_authorization(app, user),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
conn
|
||||
|> put_session(:oauth_token, token.token)
|
||||
|> redirect(to: "/web/getting-started")
|
||||
else
|
||||
_e ->
|
||||
conn
|
||||
|> render(MastodonView, "login.html", %{error: "Wrong username or password"})
|
||||
end
|
||||
end
|
||||
|
||||
def logout(conn, _) do
|
||||
conn
|
||||
|> clear_session
|
||||
|> redirect(to: "/")
|
||||
end
|
||||
|
||||
def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
Logger.debug("Unimplemented, returning unmodified relationship")
|
||||
with %User{} = target <- Repo.get(User, id) do
|
||||
|
|
@ -338,4 +607,23 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
Logger.debug("Unimplemented, returning an empty array")
|
||||
json(conn, [])
|
||||
end
|
||||
|
||||
def render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do
|
||||
actor = User.get_cached_by_ap_id(activity.data["actor"])
|
||||
created_at = NaiveDateTime.to_iso8601(created_at)
|
||||
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
|
||||
case activity.data["type"] do
|
||||
"Create" ->
|
||||
%{id: id, type: "mention", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: activity, for: user})}
|
||||
"Like" ->
|
||||
liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
|
||||
%{id: id, type: "favourite", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: liked_activity, for: user})}
|
||||
"Announce" ->
|
||||
announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
|
||||
%{id: id, type: "reblog", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: announced_activity, for: user})}
|
||||
"Follow" ->
|
||||
%{id: id, type: "follow", created_at: created_at, account: AccountView.render("account.json", %{user: actor})}
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
41
lib/pleroma/web/mastodon_api/mastodon_socket.ex
Normal file
41
lib/pleroma/web/mastodon_api/mastodon_socket.ex
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.MastodonSocket do
|
||||
use Phoenix.Socket
|
||||
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.{User, Repo}
|
||||
|
||||
transport :streaming, Phoenix.Transports.WebSocket.Raw,
|
||||
timeout: :infinity # We never receive data.
|
||||
|
||||
def connect(params, socket) do
|
||||
with token when not is_nil(token) <- params["access_token"],
|
||||
%Token{user_id: user_id} <- Repo.get_by(Token, token: token),
|
||||
%User{} = user <- Repo.get(User, user_id),
|
||||
stream when stream in ["public", "public:local", "user"] <- params["stream"] do
|
||||
socket = socket
|
||||
|> assign(:topic, params["stream"])
|
||||
|> assign(:user, user)
|
||||
Pleroma.Web.Streamer.add_socket(params["stream"], socket)
|
||||
{:ok, socket}
|
||||
else
|
||||
_e -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def id(_), do: nil
|
||||
|
||||
def handle(:text, message, _state) do
|
||||
IO.inspect message
|
||||
#| :ok
|
||||
#| state
|
||||
#| {:text, message}
|
||||
#| {:text, message, state}
|
||||
#| {:close, "Goodbye!"}
|
||||
{:text, message}
|
||||
end
|
||||
|
||||
def handle(:closed, _, %{socket: socket}) do
|
||||
topic = socket.assigns[:topic]
|
||||
Pleroma.Web.Streamer.remove_socket(topic, socket)
|
||||
end
|
||||
end
|
||||
|
|
@ -4,7 +4,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
alias Pleroma.Web.MastodonAPI.AccountView
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
|
||||
defp image_url(%{"url" => [ %{ "href" => href } | t ]}), do: href
|
||||
defp image_url(%{"url" => [ %{ "href" => href } | _ ]}), do: href
|
||||
defp image_url(_), do: nil
|
||||
|
||||
def render("accounts.json", %{users: users} = opts) do
|
||||
|
|
@ -18,7 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
header = image_url(user.info["banner"]) || "https://placehold.it/700x335"
|
||||
|
||||
%{
|
||||
id: user.id,
|
||||
id: to_string(user.id),
|
||||
username: hd(String.split(user.nickname, "@")),
|
||||
acct: user.nickname,
|
||||
display_name: user.name,
|
||||
|
|
@ -43,7 +43,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
|
||||
def render("mention.json", %{user: user}) do
|
||||
%{
|
||||
id: user.id,
|
||||
id: to_string(user.id),
|
||||
acct: user.nickname,
|
||||
username: hd(String.split(user.nickname, "@")),
|
||||
url: user.ap_id
|
||||
|
|
@ -52,10 +52,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
|
||||
def render("relationship.json", %{user: user, target: target}) do
|
||||
%{
|
||||
id: target.id,
|
||||
id: to_string(target.id),
|
||||
following: User.following?(user, target),
|
||||
followed_by: User.following?(target, user),
|
||||
blocking: false,
|
||||
blocking: User.blocks?(user, target),
|
||||
muting: false,
|
||||
requested: false,
|
||||
domain_blocking: false
|
||||
|
|
|
|||
5
lib/pleroma/web/mastodon_api/views/mastodon_view.ex
Normal file
5
lib/pleroma/web/mastodon_api/views/mastodon_view.ex
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.MastodonView do
|
||||
use Pleroma.Web, :view
|
||||
import Phoenix.HTML
|
||||
import Phoenix.HTML.Form
|
||||
end
|
||||
|
|
@ -21,9 +21,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
|> Enum.map(fn (user) -> AccountView.render("mention.json", %{user: user}) end)
|
||||
|
||||
%{
|
||||
id: activity.id,
|
||||
id: to_string(activity.id),
|
||||
uri: object,
|
||||
url: nil,
|
||||
url: nil, # TODO: This might be wrong, check with mastodon.
|
||||
account: AccountView.render("account.json", %{user: user}),
|
||||
in_reply_to_id: nil,
|
||||
in_reply_to_account_id: nil,
|
||||
|
|
@ -45,7 +45,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
name: "Web",
|
||||
website: nil
|
||||
},
|
||||
language: nil
|
||||
language: nil,
|
||||
emojis: []
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -74,10 +75,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
reply_to = Activity.get_create_activity_by_object_ap_id(object["inReplyTo"])
|
||||
reply_to_user = reply_to && User.get_cached_by_ap_id(reply_to.data["actor"])
|
||||
|
||||
emojis = (activity.data["object"]["emoji"] || [])
|
||||
|> Enum.map(fn {name, url} -> %{ shortcode: name, url: url, static_url: url } end)
|
||||
|
||||
%{
|
||||
id: activity.id,
|
||||
id: to_string(activity.id),
|
||||
uri: object["id"],
|
||||
url: object["external_url"],
|
||||
url: object["external_url"] || object["id"],
|
||||
account: AccountView.render("account.json", %{user: user}),
|
||||
in_reply_to_id: reply_to && reply_to.id,
|
||||
in_reply_to_account_id: reply_to_user && reply_to_user.id,
|
||||
|
|
@ -90,16 +94,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
favourited: !!favorited,
|
||||
muted: false,
|
||||
sensitive: sensitive,
|
||||
spoiler_text: "",
|
||||
spoiler_text: object["summary"] || "",
|
||||
visibility: "public",
|
||||
media_attachments: attachments,
|
||||
media_attachments: attachments |> Enum.take(4),
|
||||
mentions: mentions,
|
||||
tags: [], # fix,
|
||||
application: %{
|
||||
name: "Web",
|
||||
website: nil
|
||||
},
|
||||
language: nil
|
||||
language: nil,
|
||||
emojis: emojis
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -115,7 +120,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
<< hash_id::signed-32, _rest::binary >> = :crypto.hash(:md5, href)
|
||||
|
||||
%{
|
||||
id: attachment["id"] || hash_id,
|
||||
id: to_string(attachment["id"] || hash_id),
|
||||
url: href,
|
||||
remote_url: href,
|
||||
preview_url: href,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
auth: auth
|
||||
}
|
||||
else
|
||||
url = "#{redirect_uri}?code=#{auth.token}"
|
||||
connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
|
||||
url = "#{redirect_uri}#{connector}code=#{auth.token}"
|
||||
url = if params["state"] do
|
||||
url <> "&state=#{params["state"]}"
|
||||
else
|
||||
|
|
@ -40,7 +41,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
# - proper scope handling
|
||||
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
||||
with %App{} = app <- Repo.get_by(App, client_id: params["client_id"], client_secret: params["client_secret"]),
|
||||
%Authorization{} = auth <- Repo.get_by(Authorization, token: params["code"], app_id: app.id),
|
||||
fixed_token = fix_padding(params["code"]),
|
||||
%Authorization{} = auth <- Repo.get_by(Authorization, token: fixed_token, app_id: app.id),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
response = %{
|
||||
token_type: "Bearer",
|
||||
|
|
@ -50,6 +52,14 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
scope: "read write follow"
|
||||
}
|
||||
json(conn, response)
|
||||
else
|
||||
_error -> json(conn, %{error: "Invalid credentials"})
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_padding(token) do
|
||||
token
|
||||
|> Base.url_decode64!(padding: false)
|
||||
|> Base.url_encode64
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -56,9 +56,9 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
|||
|
||||
defp get_links(_activity), do: []
|
||||
|
||||
defp get_emoji_links(content) do
|
||||
Enum.map(Formatter.get_emoji(content), fn({emoji, file}) ->
|
||||
{:link, [name: to_charlist(emoji), rel: 'emoji', href: to_charlist("#{Pleroma.Web.Endpoint.static_url}#{file}")], []}
|
||||
defp get_emoji_links(emojis) do
|
||||
Enum.map(emojis, fn({emoji, file}) ->
|
||||
{:link, [name: to_charlist(emoji), rel: 'emoji', href: to_charlist(file)], []}
|
||||
end)
|
||||
end
|
||||
|
||||
|
|
@ -81,7 +81,13 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
|||
categories = (activity.data["object"]["tag"] || [])
|
||||
|> Enum.map(fn (tag) -> {:category, [term: to_charlist(tag)], []} end)
|
||||
|
||||
emoji_links = get_emoji_links(activity.data["object"]["content"] || "")
|
||||
emoji_links = get_emoji_links(activity.data["object"]["emoji"] || %{})
|
||||
|
||||
summary = if activity.data["object"]["summary"] do
|
||||
[{:summary, [], h.(activity.data["object"]["summary"])}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
[
|
||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/note']},
|
||||
|
|
@ -93,7 +99,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
|||
{:updated, h.(updated_at)},
|
||||
{:"ostatus:conversation", [ref: h.(activity.data["context"])], h.(activity.data["context"])},
|
||||
{:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []},
|
||||
] ++ get_links(activity) ++ categories ++ attachments ++ in_reply_to ++ author ++ mentions ++ emoji_links
|
||||
] ++ summary ++ get_links(activity) ++ categories ++ attachments ++ in_reply_to ++ author ++ mentions ++ emoji_links
|
||||
end
|
||||
|
||||
def to_simple_form(%{data: %{"type" => "Like"}} = activity, user, with_author) do
|
||||
|
|
@ -102,7 +108,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
|||
updated_at = activity.data["published"]
|
||||
inserted_at = activity.data["published"]
|
||||
|
||||
in_reply_to = get_in_reply_to(activity.data)
|
||||
_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
|
||||
|
||||
|
|
@ -130,7 +136,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
|||
updated_at = activity.data["published"]
|
||||
inserted_at = activity.data["published"]
|
||||
|
||||
in_reply_to = get_in_reply_to(activity.data)
|
||||
_in_reply_to = get_in_reply_to(activity.data)
|
||||
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
||||
|
||||
retweeted_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
|
||||
|
|
@ -227,6 +233,8 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
|||
] ++ author
|
||||
end
|
||||
|
||||
def to_simple_form(_, _, _), do: nil
|
||||
|
||||
def wrap_with_entry(simple_form) do
|
||||
[{
|
||||
:entry, [
|
||||
|
|
@ -238,6 +246,4 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
|||
], simple_form
|
||||
}]
|
||||
end
|
||||
|
||||
def to_simple_form(_, _, _), do: nil
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ defmodule Pleroma.Web.OStatus.FeedRepresenter do
|
|||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.OStatus.{UserRepresenter, ActivityRepresenter}
|
||||
|
||||
def to_simple_form(user, activities, users) do
|
||||
def to_simple_form(user, activities, _users) do
|
||||
most_recent_update = (List.first(activities) || user).updated_at
|
||||
|> NaiveDateTime.to_iso8601
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
defmodule Pleroma.Web.OStatus.DeleteHandler do
|
||||
require Logger
|
||||
alias Pleroma.Web.{XML, OStatus}
|
||||
alias Pleroma.{Activity, Object, Repo}
|
||||
alias Pleroma.Web.XML
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
|
||||
def handle_delete(entry, doc \\ nil) do
|
||||
def handle_delete(entry, _doc \\ nil) do
|
||||
with id <- XML.string_from_xpath("//id", entry),
|
||||
object when not is_nil(object) <- Object.get_by_ap_id(id),
|
||||
{:ok, delete} <- ActivityPub.delete(object, false) do
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
|
|||
[author] <- :xmerl_xpath.string('//author[1]', doc),
|
||||
{:ok, actor} <- OStatus.find_make_or_update_user(author),
|
||||
content_html <- OStatus.get_content(entry),
|
||||
cw <- OStatus.get_cw(entry),
|
||||
inReplyTo <- XML.string_from_xpath("//thr:in-reply-to[1]/@ref", entry),
|
||||
inReplyToActivity <- fetch_replied_to_activity(entry, inReplyTo),
|
||||
inReplyTo <- (inReplyToActivity && inReplyToActivity.data["object"]["id"]) || inReplyTo,
|
||||
|
|
@ -103,7 +104,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
|
|||
mentions <- get_mentions(entry),
|
||||
to <- make_to_list(actor, mentions),
|
||||
date <- XML.string_from_xpath("//published", entry),
|
||||
note <- CommonAPI.Utils.make_note_data(actor.ap_id, to, context, content_html, attachments, inReplyToActivity, []),
|
||||
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)),
|
||||
|
|
@ -112,7 +113,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
|
|||
note <- (if inReplyTo && !inReplyToActivity, do: note |> Map.put("inReplyTo", inReplyTo), else: note)
|
||||
do
|
||||
res = ActivityPub.create(to, actor, context, note, %{}, date, false)
|
||||
User.update_note_count(actor)
|
||||
User.increase_note_count(actor)
|
||||
res
|
||||
else
|
||||
%Activity{} = activity -> {:ok, activity}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ defmodule Pleroma.Web.OStatus do
|
|||
|
||||
alias Pleroma.{Repo, User, Web, Object, Activity}
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.{WebFinger, Websub}
|
||||
alias Pleroma.Web.OStatus.{FollowHandler, NoteHandler, DeleteHandler}
|
||||
|
||||
|
|
@ -112,7 +111,7 @@ defmodule Pleroma.Web.OStatus do
|
|||
with id when not is_nil(id) <- string_from_xpath("//activity:object[1]/id", entry),
|
||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||
{:ok, activity}
|
||||
else e ->
|
||||
else _ ->
|
||||
Logger.debug("Couldn't get, will try to fetch")
|
||||
with href when not is_nil(href) <- string_from_xpath("//activity:object[1]/link[@type=\"text/html\"]/@href", entry),
|
||||
{:ok, [favorited_activity]} <- fetch_activity_from_url(href) do
|
||||
|
|
@ -150,22 +149,28 @@ defmodule Pleroma.Web.OStatus do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Gets the content from a an entry. Will add the cw text to the body for cw'd
|
||||
Mastodon notes.
|
||||
Gets the content from a an entry.
|
||||
"""
|
||||
def get_content(entry) do
|
||||
base_content = string_from_xpath("//content", entry)
|
||||
string_from_xpath("//content", entry)
|
||||
end
|
||||
|
||||
@doc """
|
||||
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
|
||||
"<span class='mastodon-cw'>#{cw}</span><br>#{base_content}"
|
||||
else _e -> base_content
|
||||
cw
|
||||
else _e -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_tags(entry) do
|
||||
:xmerl_xpath.string('//category', entry)
|
||||
|> Enum.map(fn (category) -> string_from_xpath("/category/@term", category) |> String.downcase end)
|
||||
|> Enum.map(fn (category) -> string_from_xpath("/category/@term", category) end)
|
||||
|> Enum.filter(&(&1))
|
||||
|> Enum.map(&String.downcase/1)
|
||||
end
|
||||
|
||||
def maybe_update(doc, user) do
|
||||
|
|
@ -185,7 +190,7 @@ defmodule Pleroma.Web.OStatus do
|
|||
false <- new_data == old_data do
|
||||
change = Ecto.Changeset.change(user, new_data)
|
||||
Repo.update(change)
|
||||
else e ->
|
||||
else _ ->
|
||||
{:ok, user}
|
||||
end
|
||||
end
|
||||
|
|
@ -215,7 +220,7 @@ defmodule Pleroma.Web.OStatus do
|
|||
Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
|
||||
end
|
||||
|
||||
def make_user(uri) do
|
||||
def make_user(uri, update \\ false) do
|
||||
with {:ok, info} <- gather_user_info(uri) do
|
||||
data = %{
|
||||
name: info["name"],
|
||||
|
|
@ -225,7 +230,8 @@ defmodule Pleroma.Web.OStatus do
|
|||
avatar: info["avatar"],
|
||||
bio: info["bio"]
|
||||
}
|
||||
with %User{} = user <- User.get_by_ap_id(data.ap_id) do
|
||||
with false <- update,
|
||||
%User{} = user <- User.get_by_ap_id(data.ap_id) do
|
||||
{:ok, user}
|
||||
else _e -> insert_or_update_user(data)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
|||
alias Pleroma.Web.OStatus.{FeedRepresenter, ActivityRepresenter}
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.{OStatus, Federator}
|
||||
alias Pleroma.Web.XML
|
||||
import Ecto.Query
|
||||
|
||||
def feed_redirect(conn, %{"nickname" => nickname}) do
|
||||
|
|
@ -36,10 +37,26 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
|||
|> send_resp(200, response)
|
||||
end
|
||||
|
||||
def salmon_incoming(conn, params) do
|
||||
defp decode_or_retry(body) do
|
||||
with {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
|
||||
{:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
|
||||
{:ok, doc}
|
||||
else
|
||||
_e ->
|
||||
with [decoded | _] <- Pleroma.Web.Salmon.decode(body),
|
||||
doc <- XML.parse_document(decoded),
|
||||
uri when not is_nil(uri) <- XML.string_from_xpath("/entry/author[1]/uri", doc),
|
||||
{:ok, _} <- Pleroma.Web.OStatus.make_user(uri, true),
|
||||
{:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
|
||||
{:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
|
||||
{:ok, doc}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def salmon_incoming(conn, _) do
|
||||
{:ok, body, _conn} = read_body(conn)
|
||||
{:ok, magic_key} = Pleroma.Web.Salmon.fetch_magic_key(body)
|
||||
{:ok, doc} = Pleroma.Web.Salmon.decode_and_validate(magic_key, body)
|
||||
{:ok, doc} = decode_or_retry(body)
|
||||
|
||||
Federator.enqueue(:incoming_doc, doc)
|
||||
|
||||
|
|
@ -69,6 +86,19 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
|||
end
|
||||
end
|
||||
|
||||
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
|
||||
case get_format(conn) do
|
||||
"html" ->
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|> send_file(200, "priv/static/index.html")
|
||||
_ -> represent_activity(conn, activity, user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp represent_activity(conn, activity, user) do
|
||||
response = activity
|
||||
|> ActivityRepresenter.to_simple_form(user, true)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ defmodule Pleroma.Web.OStatus.UserRepresenter do
|
|||
{:"poco:preferredUsername", [nickname]},
|
||||
{:"poco:displayName", [name]},
|
||||
{:"poco:note", [bio]},
|
||||
{:summary, [bio]},
|
||||
{:name, [nickname]},
|
||||
{:link, [rel: 'avatar', href: avatar_url], []}
|
||||
] ++ banner
|
||||
|
|
|
|||
|
|
@ -21,6 +21,13 @@ defmodule Pleroma.Web.Router do
|
|||
plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1}
|
||||
end
|
||||
|
||||
pipeline :mastodon_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
|
||||
|
|
@ -33,6 +40,17 @@ defmodule Pleroma.Web.Router do
|
|||
plug :accepts, ["html", "json"]
|
||||
end
|
||||
|
||||
pipeline :pleroma_api do
|
||||
plug :accepts, ["html", "json"]
|
||||
end
|
||||
|
||||
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
|
||||
pipe_through :pleroma_api
|
||||
get "/password_reset/:token", UtilController, :show_password_reset
|
||||
post "/password_reset", UtilController, :password_reset
|
||||
get "/emoji", UtilController, :emoji
|
||||
end
|
||||
|
||||
scope "/oauth", Pleroma.Web.OAuth do
|
||||
get "/authorize", OAuthController, :authorize
|
||||
post "/authorize", OAuthController, :create_authorization
|
||||
|
|
@ -42,16 +60,21 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||
pipe_through :authenticated_api
|
||||
|
||||
patch "/accounts/update_credentials", MastodonAPIController, :update_credentials
|
||||
get "/accounts/verify_credentials", MastodonAPIController, :verify_credentials
|
||||
get "/accounts/relationships", MastodonAPIController, :relationships
|
||||
get "/accounts/search", MastodonAPIController, :account_search
|
||||
post "/accounts/:id/follow", MastodonAPIController, :follow
|
||||
post "/accounts/:id/unfollow", MastodonAPIController, :unfollow
|
||||
post "/accounts/:id/block", MastodonAPIController, :relationship_noop
|
||||
post "/accounts/:id/unblock", MastodonAPIController, :relationship_noop
|
||||
post "/accounts/:id/block", MastodonAPIController, :block
|
||||
post "/accounts/:id/unblock", MastodonAPIController, :unblock
|
||||
post "/accounts/:id/mute", MastodonAPIController, :relationship_noop
|
||||
post "/accounts/:id/unmute", MastodonAPIController, :relationship_noop
|
||||
|
||||
get "/blocks", MastodonAPIController, :empty_array
|
||||
post "/follows", MastodonAPIController, :follow
|
||||
|
||||
get "/blocks", MastodonAPIController, :blocks
|
||||
|
||||
get "/domain_blocks", MastodonAPIController, :empty_array
|
||||
get "/follow_requests", MastodonAPIController, :empty_array
|
||||
get "/mutes", MastodonAPIController, :empty_array
|
||||
|
|
@ -67,7 +90,10 @@ defmodule Pleroma.Web.Router do
|
|||
post "/statuses/:id/favourite", MastodonAPIController, :fav_status
|
||||
post "/statuses/:id/unfavourite", MastodonAPIController, :unfav_status
|
||||
|
||||
post "/notifications/clear", MastodonAPIController, :clear_notifications
|
||||
post "/notifications/dismiss", MastodonAPIController, :dismiss_notification
|
||||
get "/notifications", MastodonAPIController, :notifications
|
||||
get "/notifications/:id", MastodonAPIController, :get_notification
|
||||
|
||||
post "/media", MastodonAPIController, :upload
|
||||
end
|
||||
|
|
@ -76,6 +102,7 @@ defmodule Pleroma.Web.Router do
|
|||
pipe_through :api
|
||||
get "/instance", MastodonAPIController, :masto_instance
|
||||
post "/apps", MastodonAPIController, :create_app
|
||||
get "/custom_emojis", MastodonAPIController, :custom_emojis
|
||||
|
||||
get "/timelines/public", MastodonAPIController, :public_timeline
|
||||
get "/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline
|
||||
|
|
@ -113,6 +140,7 @@ defmodule Pleroma.Web.Router do
|
|||
get "/statuses/networkpublic_timeline", TwitterAPI.Controller, :public_and_external_timeline
|
||||
get "/statuses/user_timeline", TwitterAPI.Controller, :user_timeline
|
||||
get "/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline
|
||||
get "/users/show", TwitterAPI.Controller, :show_user
|
||||
|
||||
get "/statuses/show/:id", TwitterAPI.Controller, :fetch_status
|
||||
get "/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation
|
||||
|
|
@ -123,7 +151,6 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
get "/search", TwitterAPI.Controller, :search
|
||||
get "/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline
|
||||
get "/externalprofile/show", TwitterAPI.Controller, :external_profile
|
||||
end
|
||||
|
||||
scope "/api", Pleroma.Web do
|
||||
|
|
@ -149,6 +176,8 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
post "/friendships/create", TwitterAPI.Controller, :follow
|
||||
post "/friendships/destroy", TwitterAPI.Controller, :unfollow
|
||||
post "/blocks/create", TwitterAPI.Controller, :block
|
||||
post "/blocks/destroy", TwitterAPI.Controller, :unblock
|
||||
|
||||
post "/statusnet/media/upload", TwitterAPI.Controller, :upload
|
||||
post "/media/upload", TwitterAPI.Controller, :upload_json
|
||||
|
|
@ -161,6 +190,12 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
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
|
||||
|
||||
get "/mutes/users/ids", TwitterAPI.Controller, :empty_array
|
||||
|
||||
get "/externalprofile/show", TwitterAPI.Controller, :external_profile
|
||||
end
|
||||
|
||||
pipeline :ostatus do
|
||||
|
|
@ -172,6 +207,7 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
get "/objects/:uuid", OStatus.OStatusController, :object
|
||||
get "/activities/:uuid", OStatus.OStatusController, :activity
|
||||
get "/notice/:id", OStatus.OStatusController, :notice
|
||||
|
||||
get "/users/:nickname/feed", OStatus.OStatusController, :feed
|
||||
get "/users/:nickname", OStatus.OStatusController, :feed_redirect
|
||||
|
|
@ -188,6 +224,15 @@ defmodule Pleroma.Web.Router do
|
|||
get "/webfinger", WebFinger.WebFingerController, :webfinger
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.MastodonAPI do
|
||||
pipe_through :mastodon_html
|
||||
|
||||
get "/web/login", MastodonAPIController, :login
|
||||
post "/web/login", MastodonAPIController, :login_post
|
||||
get "/web/*path", MastodonAPIController, :index
|
||||
delete "/auth/sign_out", MastodonAPIController, :logout
|
||||
end
|
||||
|
||||
scope "/", Fallback do
|
||||
get "/*path", RedirectController, :redirector
|
||||
end
|
||||
|
|
|
|||
|
|
@ -73,17 +73,30 @@ defmodule Pleroma.Web.Salmon do
|
|||
"RSA.#{modulus_enc}.#{exponent_enc}"
|
||||
end
|
||||
|
||||
def generate_rsa_pem do
|
||||
port = Port.open({:spawn, "openssl genrsa"}, [:binary])
|
||||
{:ok, pem} = receive do
|
||||
{^port, {:data, pem}} -> {:ok, pem}
|
||||
end
|
||||
Port.close(port)
|
||||
if Regex.match?(~r/RSA PRIVATE KEY/, pem) do
|
||||
# Native generation of RSA keys is only available since OTP 20+ and in default build conditions
|
||||
# We try at compile time to generate natively an RSA key otherwise we fallback on the old way.
|
||||
try do
|
||||
_ = :public_key.generate_key({:rsa, 2048, 65537})
|
||||
def generate_rsa_pem do
|
||||
key = :public_key.generate_key({:rsa, 2048, 65537})
|
||||
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
|
||||
pem = :public_key.pem_encode([entry]) |> String.trim_trailing
|
||||
{:ok, pem}
|
||||
else
|
||||
:error
|
||||
end
|
||||
rescue
|
||||
_ ->
|
||||
def generate_rsa_pem do
|
||||
port = Port.open({:spawn, "openssl genrsa"}, [:binary])
|
||||
{:ok, pem} = receive do
|
||||
{^port, {:data, pem}} -> {:ok, pem}
|
||||
end
|
||||
Port.close(port)
|
||||
if Regex.match?(~r/RSA PRIVATE KEY/, pem) do
|
||||
{:ok, pem}
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def keys_from_pem(pem) do
|
||||
|
|
|
|||
112
lib/pleroma/web/streamer.ex
Normal file
112
lib/pleroma/web/streamer.ex
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
defmodule Pleroma.Web.Streamer do
|
||||
use GenServer
|
||||
require Logger
|
||||
alias Pleroma.{User, Notification}
|
||||
|
||||
def start_link do
|
||||
spawn(fn ->
|
||||
Process.sleep(1000 * 30) # 30 seconds
|
||||
GenServer.cast(__MODULE__, %{action: :ping})
|
||||
end)
|
||||
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
|
||||
end
|
||||
|
||||
def add_socket(topic, socket) do
|
||||
GenServer.cast(__MODULE__, %{action: :add, socket: socket, topic: topic})
|
||||
end
|
||||
|
||||
def remove_socket(topic, socket) do
|
||||
GenServer.cast(__MODULE__, %{action: :remove, socket: socket, topic: topic})
|
||||
end
|
||||
|
||||
def stream(topic, item) do
|
||||
GenServer.cast(__MODULE__, %{action: :stream, topic: topic, item: item})
|
||||
end
|
||||
|
||||
def handle_cast(%{action: :ping}, topics) do
|
||||
Map.values(topics)
|
||||
|> List.flatten
|
||||
|> Enum.each(fn (socket) ->
|
||||
Logger.debug("Sending keepalive ping")
|
||||
send socket.transport_pid, {:text, ""}
|
||||
end)
|
||||
spawn(fn ->
|
||||
Process.sleep(1000 * 30) # 30 seconds
|
||||
GenServer.cast(__MODULE__, %{action: :ping})
|
||||
end)
|
||||
{:noreply, topics}
|
||||
end
|
||||
|
||||
def handle_cast(%{action: :stream, topic: "user", item: %Notification{} = item}, topics) do
|
||||
topic = "user:#{item.user_id}"
|
||||
Enum.each(topics[topic] || [], fn (socket) ->
|
||||
json = %{
|
||||
event: "notification",
|
||||
payload: Pleroma.Web.MastodonAPI.MastodonAPIController.render_notification(socket.assigns["user"], item) |> Poison.encode!
|
||||
} |> Poison.encode!
|
||||
|
||||
send socket.transport_pid, {:text, json}
|
||||
end)
|
||||
{:noreply, topics}
|
||||
end
|
||||
|
||||
def handle_cast(%{action: :stream, topic: "user", item: item}, topics) do
|
||||
Logger.debug("Trying to push to users")
|
||||
recipient_topics = User.get_recipients_from_activity(item)
|
||||
|> Enum.map(fn (%{id: id}) -> "user:#{id}" end)
|
||||
|
||||
Enum.each(recipient_topics, fn (topic) ->
|
||||
push_to_socket(topics, topic, item)
|
||||
end)
|
||||
{:noreply, topics}
|
||||
end
|
||||
|
||||
def handle_cast(%{action: :stream, topic: topic, item: item}, topics) do
|
||||
Logger.debug("Trying to push to #{topic}")
|
||||
Logger.debug("Pushing item to #{topic}")
|
||||
push_to_socket(topics, topic, item)
|
||||
{:noreply, topics}
|
||||
end
|
||||
|
||||
def handle_cast(%{action: :add, topic: topic, socket: socket}, sockets) do
|
||||
topic = internal_topic(topic, socket)
|
||||
sockets_for_topic = sockets[topic] || []
|
||||
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
|
||||
|
||||
def handle_cast(%{action: :remove, topic: topic, socket: socket}, sockets) do
|
||||
topic = internal_topic(topic, socket)
|
||||
sockets_for_topic = sockets[topic] || []
|
||||
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)}")
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def push_to_socket(topics, topic, item) do
|
||||
Enum.each(topics[topic] || [], fn (socket) ->
|
||||
json = %{
|
||||
event: "update",
|
||||
payload: Pleroma.Web.MastodonAPI.StatusView.render("status.json", activity: item, for: socket.assigns[:user]) |> Poison.encode!
|
||||
} |> Poison.encode!
|
||||
|
||||
send socket.transport_pid, {:text, json}
|
||||
end)
|
||||
end
|
||||
|
||||
defp internal_topic("user", socket) do
|
||||
"user:#{socket.assigns[:user].id}"
|
||||
end
|
||||
|
||||
defp internal_topic(topic, _), do: topic
|
||||
end
|
||||
|
|
@ -3,9 +3,73 @@
|
|||
<head>
|
||||
<meta charset=utf-8 />
|
||||
<title>Pleroma</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: #282c37;
|
||||
font-family: sans-serif;
|
||||
color:white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 50px auto;
|
||||
max-width: 320px;
|
||||
padding: 0;
|
||||
padding: 40px 40px 40px 40px;
|
||||
background-color: #313543;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #9baec8;
|
||||
font-weight: normal;
|
||||
font-size: 20px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-top: 20px;
|
||||
background-color: rgba(0,0,0,.1);
|
||||
color: white;
|
||||
border: 0;
|
||||
border-bottom: 2px solid #9baec8;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
border-bottom: 2px solid #4b8ed8;
|
||||
}
|
||||
|
||||
button {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
color: white;
|
||||
background-color: #419bdd;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
padding: 10px;
|
||||
margin-top: 30px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to Pleroma</h1>
|
||||
<%= render @view_module, @view_template, assigns %>
|
||||
<div class="container">
|
||||
<h1>Pleroma</h1>
|
||||
<%= render @view_module, @view_template, assigns %>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta content='width=device-width, initial-scale=1' name='viewport'>
|
||||
<link rel="stylesheet" media="all" href="/packs/common.css" />
|
||||
<link rel="stylesheet" media="all" href="/packs/default.css" />
|
||||
<link rel="stylesheet" media="all" href="/packs/pl-dark-masto-fe.css" />
|
||||
|
||||
<script src="/packs/common.js"></script>
|
||||
<script src="/packs/locale_en.js"></script>
|
||||
<script id='initial-state' type='application/json'><%= raw @initial_state %></script>
|
||||
<script src="/packs/application.js"></script>
|
||||
</head>
|
||||
<body class='app-body'>
|
||||
<div class='app-holder' data-props='{"locale":"en"}' id='mastodon'>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<h2>Login in to Mastodon Frontend</h2>
|
||||
<%= if @error do %>
|
||||
<h2><%= @error %></h2>
|
||||
<% end %>
|
||||
<%= form_for @conn, mastodon_api_path(@conn, :login), [as: "authorization"], fn f -> %>
|
||||
<%= text_input f, :name, placeholder: "Username" %>
|
||||
<br>
|
||||
<%= password_input f, :password, placeholder: "Password" %>
|
||||
<br>
|
||||
<%= submit "Log in" %>
|
||||
<% end %>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<h2>Invalid Token</h2>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<h2>Password Reset for <%= @user.nickname %></h2>
|
||||
<%= form_for @conn, util_path(@conn, :password_reset), [as: "data"], fn f -> %>
|
||||
<%= label f, :password, "Password" %>
|
||||
<%= password_input f, :password %>
|
||||
<br>
|
||||
|
||||
<%= label f, :password_confirmation, "Confirmation" %>
|
||||
<%= password_input f, :password_confirmation %>
|
||||
<br>
|
||||
<%= hidden_input f, :token, value: @token.token %>
|
||||
<%= submit "Reset" %>
|
||||
<% end %>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<h2>Password reset failed</h2>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<h2>Password changed!</h2>
|
||||
|
|
@ -1,6 +1,29 @@
|
|||
defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||
use Pleroma.Web, :controller
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Formatter
|
||||
|
||||
alias Pleroma.{Repo, PasswordResetToken, User}
|
||||
|
||||
def show_password_reset(conn, %{"token" => token}) do
|
||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||
%User{} = user <- Repo.get(User, token.user_id) do
|
||||
render conn, "password_reset.html", %{
|
||||
token: token,
|
||||
user: user
|
||||
}
|
||||
else
|
||||
_e -> render conn, "invalid_token.html"
|
||||
end
|
||||
end
|
||||
|
||||
def password_reset(conn, %{"data" => data}) do
|
||||
with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do
|
||||
render conn, "password_reset_success.html"
|
||||
else
|
||||
_e -> render conn, "password_reset_failed.html"
|
||||
end
|
||||
end
|
||||
|
||||
def help_test(conn, _params) do
|
||||
json(conn, "ok")
|
||||
|
|
@ -46,4 +69,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
_ -> json(conn, version)
|
||||
end
|
||||
end
|
||||
|
||||
def emoji(conn, _params) do
|
||||
json conn, Enum.into(Formatter.get_custom_emoji(), %{})
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
|
|||
}
|
||||
end
|
||||
|
||||
def to_map(%Activity{data: %{"type" => "Delete", "published" => created_at, "object" => deleted_object }} = activity, %{user: user} = opts) do
|
||||
def to_map(%Activity{data: %{"type" => "Delete", "published" => created_at, "object" => _ }} = activity, %{user: user} = opts) do
|
||||
created_at = created_at |> Utils.date_to_asctime
|
||||
|
||||
%{
|
||||
|
|
@ -135,6 +135,13 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
|
|||
tags = activity.data["object"]["tag"] || []
|
||||
possibly_sensitive = Enum.member?(tags, "nsfw")
|
||||
|
||||
summary = activity.data["object"]["summary"]
|
||||
content = if !!summary and summary != "" do
|
||||
"<span>#{activity.data["object"]["summary"]}</span><br />#{content}</span>"
|
||||
else
|
||||
content
|
||||
end
|
||||
|
||||
html = HtmlSanitizeEx.basic_html(content) |> Formatter.emojify(object["emoji"])
|
||||
|
||||
%{
|
||||
|
|
|
|||
|
|
@ -4,27 +4,29 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter
|
||||
alias Pleroma.Web.TwitterAPI.UserView
|
||||
alias Pleroma.Web.{OStatus, CommonAPI}
|
||||
alias Pleroma.Formatter
|
||||
import Ecto.Query
|
||||
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
|
||||
def create_status(%User{} = user, %{"status" => status} = data) do
|
||||
def create_status(%User{} = user, %{"status" => _} = data) do
|
||||
CommonAPI.post(user, data)
|
||||
end
|
||||
|
||||
def fetch_friend_statuses(user, opts \\ %{}) do
|
||||
opts = Map.put(opts, "blocking_user", user)
|
||||
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)
|
||||
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)
|
||||
ActivityPub.fetch_public_activities(opts)
|
||||
|> activities_to_statuses(%{for: user})
|
||||
end
|
||||
|
|
@ -41,7 +43,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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),
|
||||
activities <- ActivityPub.fetch_activities_for_context(context, %{"blocking_user" => user}),
|
||||
statuses <- activities |> activities_to_statuses(%{for: user})
|
||||
do
|
||||
statuses
|
||||
|
|
@ -83,6 +85,26 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
end
|
||||
end
|
||||
|
||||
def block(%User{} = blocker, params) do
|
||||
with {:ok, %User{} = blocked} <- get_user(params),
|
||||
{:ok, blocker} <- User.block(blocker, blocked)
|
||||
do
|
||||
{:ok, blocker, blocked}
|
||||
else
|
||||
err -> err
|
||||
end
|
||||
end
|
||||
|
||||
def unblock(%User{} = blocker, params) do
|
||||
with {:ok, %User{} = blocked} <- get_user(params),
|
||||
{:ok, blocker} <- User.unblock(blocker, blocked)
|
||||
do
|
||||
{:ok, blocker, blocked}
|
||||
else
|
||||
err -> err
|
||||
end
|
||||
end
|
||||
|
||||
def repeat(%User{} = user, ap_id_or_id) do
|
||||
with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(ap_id_or_id, user),
|
||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id),
|
||||
|
|
@ -193,7 +215,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
end
|
||||
end
|
||||
|
||||
defp parse_int(string, default \\ nil)
|
||||
defp parse_int(string, default)
|
||||
defp parse_int(string, default) when is_binary(string) do
|
||||
with {n, _} <- Integer.parse(string) do
|
||||
n
|
||||
|
|
|
|||
|
|
@ -3,17 +3,18 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView}
|
||||
alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.{Repo, Activity, User, Object}
|
||||
alias Pleroma.{Repo, Activity, User}
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Ecto.Changeset
|
||||
|
||||
require Logger
|
||||
|
||||
def verify_credentials(%{assigns: %{user: user}} = conn, _params) do
|
||||
render(conn, UserView, "show.json", %{user: user})
|
||||
token = Phoenix.Token.sign(conn, "user socket", user.id)
|
||||
render(conn, UserView, "show.json", %{user: user, token: token})
|
||||
end
|
||||
|
||||
def status_update(%{assigns: %{user: user}} = conn, %{"status" => status_text} = status_data) do
|
||||
def status_update(%{assigns: %{user: user}} = conn, %{"status" => _} = status_data) do
|
||||
with media_ids <- extract_media_ids(status_data),
|
||||
{:ok, activity} <- TwitterAPI.create_status(user, Map.put(status_data, "media_ids", media_ids)) do
|
||||
conn
|
||||
|
|
@ -65,10 +66,23 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
|> json_reply(200, json)
|
||||
end
|
||||
|
||||
def show_user(conn, params) do
|
||||
with {:ok, shown} <- TwitterAPI.get_user(params) do
|
||||
if user = conn.assigns.user do
|
||||
render conn, UserView, "show.json", %{user: shown, for: user}
|
||||
else
|
||||
render conn, UserView, "show.json", %{user: shown}
|
||||
end
|
||||
else
|
||||
{:error, msg} ->
|
||||
bad_request_reply(conn, msg)
|
||||
end
|
||||
end
|
||||
|
||||
def user_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||
case TwitterAPI.get_user(user, params) do
|
||||
{:ok, target_user} ->
|
||||
params = Map.merge(params, %{"actor_id" => target_user.ap_id})
|
||||
params = Map.merge(params, %{"actor_id" => target_user.ap_id, "whole_db" => true})
|
||||
statuses = TwitterAPI.fetch_user_statuses(user, params)
|
||||
conn
|
||||
|> json_reply(200, statuses |> Poison.encode!)
|
||||
|
|
@ -93,6 +107,22 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
end
|
||||
end
|
||||
|
||||
def block(%{assigns: %{user: user}} = conn, params) do
|
||||
case TwitterAPI.block(user, params) do
|
||||
{:ok, user, blocked} ->
|
||||
render conn, UserView, "show.json", %{user: blocked, for: user}
|
||||
{:error, msg} -> forbidden_json_reply(conn, msg)
|
||||
end
|
||||
end
|
||||
|
||||
def unblock(%{assigns: %{user: user}} = conn, params) do
|
||||
case TwitterAPI.unblock(user, params) do
|
||||
{:ok, user, blocked} ->
|
||||
render conn, UserView, "show.json", %{user: blocked, for: user}
|
||||
{:error, msg} -> forbidden_json_reply(conn, msg)
|
||||
end
|
||||
end
|
||||
|
||||
def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with {:ok, delete} <- CommonAPI.delete(id, user) do
|
||||
json = ActivityRepresenter.to_json(delete, %{user: user, for: user})
|
||||
|
|
@ -186,8 +216,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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
|
||||
%{"url" => [ %{ "href" => href } | t ]} = object.data
|
||||
{:ok, _user} <- Repo.update(change) do
|
||||
%{"url" => [ %{ "href" => href } | _ ]} = object.data
|
||||
response = %{ url: href } |> Poison.encode!
|
||||
conn
|
||||
|> json_reply(200, response)
|
||||
|
|
@ -198,8 +228,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller 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
|
||||
%{"url" => [ %{ "href" => href } | t ]} = object.data
|
||||
{:ok, _user} <- Repo.update(change) do
|
||||
%{"url" => [ %{ "href" => href } | _ ]} = object.data
|
||||
response = %{ url: href } |> Poison.encode!
|
||||
conn
|
||||
|> json_reply(200, response)
|
||||
|
|
@ -225,7 +255,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
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} <- Repo.update(changeset) do
|
||||
conn
|
||||
|> json_reply(200, Poison.encode!(mrn))
|
||||
else
|
||||
|
|
@ -249,6 +279,22 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
end
|
||||
end
|
||||
|
||||
def friends_ids(%{assigns: %{user: user}} = conn, _params) do
|
||||
with {:ok, friends} <- User.get_friends(user) do
|
||||
ids = friends
|
||||
|> Enum.map(fn x -> x.id end)
|
||||
|> Poison.encode!
|
||||
|
||||
json(conn, ids)
|
||||
else
|
||||
_e -> bad_request_reply(conn, "Can't get friends")
|
||||
end
|
||||
end
|
||||
|
||||
def empty_array(conn, _params) do
|
||||
json(conn, Poison.encode!([]))
|
||||
end
|
||||
|
||||
def update_profile(%{assigns: %{user: user}} = conn, params) do
|
||||
params = if bio = params["description"] do
|
||||
Map.put(params, "bio", bio)
|
||||
|
|
@ -266,7 +312,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
end
|
||||
end
|
||||
|
||||
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do
|
||||
conn
|
||||
|> json(TwitterAPI.search(user, params))
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,25 +11,28 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
|
|||
render_many(users, Pleroma.Web.TwitterAPI.UserView, "user.json", for: user)
|
||||
end
|
||||
|
||||
defp image_url(%{"url" => [ %{ "href" => href } | t ]}), do: href
|
||||
defp image_url(_), do: nil
|
||||
|
||||
def render("user.json", %{user: user = %User{}} = assigns) do
|
||||
image = User.avatar_url(user)
|
||||
following = if assigns[:for] do
|
||||
User.following?(assigns[:for], user)
|
||||
{following, follows_you, statusnet_blocking} = if assigns[:for] do
|
||||
{
|
||||
User.following?(assigns[:for], user),
|
||||
User.following?(user, assigns[:for]),
|
||||
User.blocks?(assigns[:for], user)
|
||||
}
|
||||
else
|
||||
false
|
||||
{false, false, false}
|
||||
end
|
||||
|
||||
user_info = User.get_cached_user_info(user)
|
||||
|
||||
%{
|
||||
data = %{
|
||||
"created_at" => user.inserted_at |> Utils.format_naive_asctime,
|
||||
"description" => HtmlSanitizeEx.strip_tags(user.bio),
|
||||
"favourites_count" => 0,
|
||||
"followers_count" => user_info[:follower_count],
|
||||
"following" => following,
|
||||
"follows_you" => follows_you,
|
||||
"statusnet_blocking" => statusnet_blocking,
|
||||
"friends_count" => user_info[:following_count],
|
||||
"id" => user.id,
|
||||
"name" => user.name,
|
||||
|
|
@ -44,6 +47,12 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
|
|||
"cover_photo" => image_url(user.info["banner"]),
|
||||
"background_image" => image_url(user.info["background"])
|
||||
}
|
||||
|
||||
if assigns[:token] do
|
||||
Map.put(data, "token", assigns[:token])
|
||||
else
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
def render("short.json", %{user: %User{
|
||||
|
|
@ -57,4 +66,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
|
|||
"screen_name" => nickname
|
||||
}
|
||||
end
|
||||
|
||||
defp image_url(%{"url" => [ %{ "href" => href } | _ ]}), do: href
|
||||
defp image_url(_), do: nil
|
||||
end
|
||||
|
|
|
|||
4
lib/pleroma/web/twitter_api/views/util_view.ex
Normal file
4
lib/pleroma/web/twitter_api/views/util_view.ex
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
defmodule Pleroma.Web.TwitterAPI.UtilView do
|
||||
use Pleroma.Web, :view
|
||||
import Phoenix.HTML.Form
|
||||
end
|
||||
|
|
@ -89,7 +89,7 @@ defmodule Pleroma.Web.WebFinger do
|
|||
with {:ok, %{status_code: status_code, body: body}} when status_code in 200..299 <- @httpoison.get("http://#{domain}/.well-known/host-meta", [], follow_redirect: true) do
|
||||
get_template_from_xml(body)
|
||||
else
|
||||
e ->
|
||||
_ ->
|
||||
with {:ok, %{body: body}} <- @httpoison.get("https://#{domain}/.well-known/host-meta", []) do
|
||||
get_template_from_xml(body)
|
||||
else
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ defmodule Pleroma.Web.Websub do
|
|||
do
|
||||
changeset = Changeset.change(subscription, %{state: "active"})
|
||||
Repo.update(changeset)
|
||||
else _e ->
|
||||
changeset = Changeset.change(subscription, %{state: "rejected"})
|
||||
{:ok, subscription} = Repo.update(changeset)
|
||||
else e ->
|
||||
Logger.debug("Couldn't verify subscription")
|
||||
Logger.debug(inspect(e))
|
||||
{:error, subscription}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Pleroma.Web.XML do
|
||||
require Logger
|
||||
|
||||
def string_from_xpath(xpath, :error), do: nil
|
||||
def string_from_xpath(_, :error), do: nil
|
||||
def string_from_xpath(xpath, doc) do
|
||||
{:xmlObj, :string, res} = :xmerl_xpath.string('string(#{xpath})', doc)
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ defmodule Pleroma.Web.XML do
|
|||
|
||||
doc
|
||||
catch
|
||||
:exit, error ->
|
||||
:exit, _error ->
|
||||
Logger.debug("Couldn't parse xml: #{inspect(text)}")
|
||||
:error
|
||||
end
|
||||
|
|
|
|||
77
lib/transports.ex
Normal file
77
lib/transports.ex
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
defmodule Phoenix.Transports.WebSocket.Raw do
|
||||
import Plug.Conn, only: [
|
||||
fetch_query_params: 1,
|
||||
send_resp: 3
|
||||
]
|
||||
alias Phoenix.Socket.Transport
|
||||
|
||||
def default_config do
|
||||
[
|
||||
timeout: 60_000,
|
||||
transport_log: false,
|
||||
cowboy: Phoenix.Endpoint.CowboyWebSocket
|
||||
]
|
||||
end
|
||||
|
||||
def init(%Plug.Conn{method: "GET"} = conn, {endpoint, handler, transport}) do
|
||||
{_, opts} = handler.__transport__(transport)
|
||||
|
||||
conn = conn
|
||||
|> fetch_query_params
|
||||
|> Transport.transport_log(opts[:transport_log])
|
||||
|> Transport.force_ssl(handler, endpoint, opts)
|
||||
|> Transport.check_origin(handler, endpoint, opts)
|
||||
|
||||
case conn do
|
||||
%{halted: false} = conn ->
|
||||
case Transport.connect(endpoint, handler, transport, __MODULE__, nil, conn.params) do
|
||||
{:ok, socket} ->
|
||||
{:ok, conn, {__MODULE__, {socket, opts}}}
|
||||
:error ->
|
||||
send_resp(conn, :forbidden, "")
|
||||
{:error, conn}
|
||||
end
|
||||
_ ->
|
||||
{:error, conn}
|
||||
end
|
||||
end
|
||||
|
||||
def init(conn, _) do
|
||||
send_resp(conn, :bad_request, "")
|
||||
{:error, conn}
|
||||
end
|
||||
|
||||
def ws_init({socket, config}) do
|
||||
Process.flag(:trap_exit, true)
|
||||
{:ok, %{socket: socket}, config[:timeout]}
|
||||
end
|
||||
|
||||
def ws_handle(op, data, state) do
|
||||
state.socket.handler
|
||||
|> apply(:handle, [op, data, state])
|
||||
|> case do
|
||||
{op, data} ->
|
||||
{:reply, {op, data}, state}
|
||||
{op, data, state} ->
|
||||
{:reply, {op, data}, state}
|
||||
%{} = state ->
|
||||
{:ok, state}
|
||||
_ ->
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
|
||||
def ws_info({_,_} = tuple, state) do
|
||||
{:reply, tuple, state}
|
||||
end
|
||||
|
||||
def ws_info(_tuple, state), do: {:ok, state}
|
||||
|
||||
def ws_close(state) do
|
||||
ws_handle(:closed, :normal, state)
|
||||
end
|
||||
|
||||
def ws_terminate(reason, state) do
|
||||
ws_handle(:closed, reason, state)
|
||||
end
|
||||
end
|
||||
|
|
@ -37,6 +37,6 @@ defmodule Pleroma.XmlBuilder do
|
|||
"#{attribute}=\"#{value}\""
|
||||
end |> Enum.join(" ")
|
||||
|
||||
[tag, attributes_string] |> Enum.join(" ") |> String.strip
|
||||
[tag, attributes_string] |> Enum.join(" ") |> String.trim
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue