Merge remote-tracking branch 'origin/develop' into shigusegubu
* origin/develop: (207 commits) Update README.md Mastodon API: Fix list streaming nginx example config: remove CORS headers, now managed by CORSPlug. config: properly configure CORSPlug. oauth: fix token decode regression tests: add test for internal data stripping activitypub: transmogrifier: sanitize internal representation details from outgoing objects lib/mix/tasks: s/@doc/@moduledoc/ lib/mix/tasks/unsubscribe_user.ex: Fix syntax from bad line copy lib/mix/tasks: Add remaining documentation for mix tasks config/config.md: Add lines inspired/copied from CONFIGURATION.md README.md: Add note about config/config.md README.md: Put the systemd’s .service note to the relevant section README.md: Add note for OpenRC config/config.md: scope_options_enabled also addresses subject config/config.md: Fill all the blanks config/config.md: Complete it [WIP] config/config.md: Create Document the mix tasks in ex_doc instead Document mix tasks ...
This commit is contained in:
commit
bc0f261f72
127 changed files with 3151 additions and 912 deletions
|
|
@ -2,7 +2,13 @@ defmodule Mix.Tasks.DeactivateUser do
|
|||
use Mix.Task
|
||||
alias Pleroma.User
|
||||
|
||||
@shortdoc "Toggle deactivation status for a user"
|
||||
@moduledoc """
|
||||
Deactivates a user (local or remote)
|
||||
|
||||
Usage: ``mix deactivate_user <nickname>``
|
||||
|
||||
Example: ``mix deactivate_user lain``
|
||||
"""
|
||||
def run([nickname]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
defmodule Mix.Tasks.GenerateConfig do
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "Generates a new config"
|
||||
@moduledoc """
|
||||
Generate a new config
|
||||
|
||||
## Usage
|
||||
``mix generate_config``
|
||||
|
||||
This mix task is interactive, and will overwrite the config present at ``config/generated_config.exs``.
|
||||
"""
|
||||
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
defmodule Mix.Tasks.GenerateInviteToken do
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "Generate invite token for user"
|
||||
@moduledoc """
|
||||
Generates invite token
|
||||
|
||||
This is in the form of a URL to be used by the Invited user to register themselves.
|
||||
|
||||
## Usage
|
||||
``mix generate_invite_token``
|
||||
"""
|
||||
def run([]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@ defmodule Mix.Tasks.GeneratePasswordReset do
|
|||
use Mix.Task
|
||||
alias Pleroma.User
|
||||
|
||||
@shortdoc "Generate password reset link for user"
|
||||
@moduledoc """
|
||||
Generate password reset link for user
|
||||
|
||||
Usage: ``mix generate_password_reset <nickname>``
|
||||
|
||||
Example: ``mix generate_password_reset lain``
|
||||
"""
|
||||
def run([nickname]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
defmodule Mix.Tasks.SetModerator do
|
||||
@moduledoc """
|
||||
Set moderator to a local user
|
||||
|
||||
Usage: ``mix set_moderator <nickname>``
|
||||
|
||||
Example: ``mix set_moderator lain``
|
||||
"""
|
||||
|
||||
use Mix.Task
|
||||
import Mix.Ecto
|
||||
alias Pleroma.{Repo, User}
|
||||
|
||||
@shortdoc "Set moderator status"
|
||||
def run([nickname | rest]) do
|
||||
Application.ensure_all_started(:pleroma)
|
||||
|
||||
|
|
|
|||
19
lib/mix/tasks/reactivate_user.ex
Normal file
19
lib/mix/tasks/reactivate_user.ex
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
defmodule Mix.Tasks.ReactivateUser do
|
||||
use Mix.Task
|
||||
alias Pleroma.User
|
||||
|
||||
@moduledoc """
|
||||
Reactivate a user
|
||||
|
||||
Usage: ``mix reactivate_user <nickname>``
|
||||
|
||||
Example: ``mix reactivate_user lain``
|
||||
"""
|
||||
def run([nickname]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
with user <- User.get_by_nickname(nickname) do
|
||||
User.deactivate(user, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,4 +1,12 @@
|
|||
defmodule Mix.Tasks.RegisterUser do
|
||||
@moduledoc """
|
||||
Manually register a local user
|
||||
|
||||
Usage: ``mix register_user <name> <nickname> <email> <bio> <password>``
|
||||
|
||||
Example: ``mix register_user 仮面の告白 lain lain@example.org "blushy-crushy fediverse idol + pleroma dev" pleaseDontHeckLain``
|
||||
"""
|
||||
|
||||
use Mix.Task
|
||||
alias Pleroma.{Repo, User}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@ defmodule Mix.Tasks.RelayFollow do
|
|||
alias Pleroma.Web.ActivityPub.Relay
|
||||
|
||||
@shortdoc "Follows a remote relay"
|
||||
@moduledoc """
|
||||
Follows a remote relay
|
||||
|
||||
Usage: ``mix relay_follow <relay_url>``
|
||||
|
||||
Example: ``mix relay_follow https://example.org/relay``
|
||||
"""
|
||||
def run([target]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,13 @@ defmodule Mix.Tasks.RelayUnfollow do
|
|||
require Logger
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
|
||||
@shortdoc "Follows a remote relay"
|
||||
@moduledoc """
|
||||
Unfollows a remote relay
|
||||
|
||||
Usage: ``mix relay_follow <relay_url>``
|
||||
|
||||
Example: ``mix relay_follow https://example.org/relay``
|
||||
"""
|
||||
def run([target]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,18 @@ defmodule Mix.Tasks.RmUser do
|
|||
use Mix.Task
|
||||
alias Pleroma.User
|
||||
|
||||
@shortdoc "Permanently delete a user"
|
||||
@moduledoc """
|
||||
Permanently deletes a user
|
||||
|
||||
Usage: ``mix rm_user [nickname]``
|
||||
|
||||
Example: ``mix rm_user lain``
|
||||
"""
|
||||
def run([nickname]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
with %User{local: true} = user <- User.get_by_nickname(nickname) do
|
||||
User.delete(user)
|
||||
{:ok, _} = User.delete(user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
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;
|
||||
CREATE USER pleroma WITH ENCRYPTED PASSWORD '<%= dbpass %>';
|
||||
CREATE DATABASE pleroma_dev OWNER pleroma;
|
||||
\c pleroma_dev;
|
||||
--Extensions made by ecto.migrate that need superuser access
|
||||
CREATE EXTENSION IF NOT EXISTS citext;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,18 @@
|
|||
defmodule Mix.Tasks.SetLocked do
|
||||
@moduledoc """
|
||||
Lock a local user
|
||||
|
||||
The local user will then have to manually accept/reject followers. This can also be done by the user into their settings.
|
||||
|
||||
Usage: ``mix set_locked <username>``
|
||||
|
||||
Example: ``mix set_locked lain``
|
||||
"""
|
||||
|
||||
use Mix.Task
|
||||
import Mix.Ecto
|
||||
alias Pleroma.{Repo, User}
|
||||
|
||||
@shortdoc "Set locked status"
|
||||
def run([nickname | rest]) do
|
||||
ensure_started(Repo, [])
|
||||
|
||||
|
|
|
|||
38
lib/mix/tasks/unsubscribe_user.ex
Normal file
38
lib/mix/tasks/unsubscribe_user.ex
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
defmodule Mix.Tasks.UnsubscribeUser do
|
||||
use Mix.Task
|
||||
alias Pleroma.{User, Repo}
|
||||
require Logger
|
||||
|
||||
@moduledoc """
|
||||
Deactivate and Unsubscribe local users from a user
|
||||
|
||||
Usage: ``mix unsubscribe_user <nickname>``
|
||||
|
||||
Example: ``mix unsubscribe_user lain``
|
||||
"""
|
||||
def run([nickname]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
with %User{} = user <- User.get_by_nickname(nickname) do
|
||||
Logger.info("Deactivating #{user.nickname}")
|
||||
User.deactivate(user)
|
||||
|
||||
{:ok, friends} = User.get_friends(user)
|
||||
|
||||
Enum.each(friends, fn friend ->
|
||||
user = Repo.get(User, user.id)
|
||||
|
||||
Logger.info("Unsubscribing #{friend.nickname} from #{user.nickname}")
|
||||
User.unfollow(user, friend)
|
||||
end)
|
||||
|
||||
:timer.sleep(500)
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
|
||||
if length(user.following) == 0 do
|
||||
Logger.info("Successfully unsubscribed all followers from #{user.nickname}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -82,4 +82,10 @@ defmodule Pleroma.Activity do
|
|||
def normalize(obj) when is_map(obj), do: Activity.get_by_ap_id(obj["id"])
|
||||
def normalize(ap_id) when is_binary(ap_id), do: Activity.get_by_ap_id(ap_id)
|
||||
def normalize(_), do: nil
|
||||
|
||||
def get_in_reply_to_activity(%Activity{data: %{"object" => %{"inReplyTo" => ap_id}}}) do
|
||||
get_create_activity_by_object_ap_id(ap_id)
|
||||
end
|
||||
|
||||
def get_in_reply_to_activity(_), do: nil
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,18 +12,35 @@ defmodule Pleroma.Application do
|
|||
[
|
||||
# Start the Ecto repository
|
||||
supervisor(Pleroma.Repo, []),
|
||||
worker(Pleroma.Emoji, []),
|
||||
# Start the endpoint when the application starts
|
||||
supervisor(Pleroma.Web.Endpoint, []),
|
||||
# Start your own worker by calling: Pleroma.Worker.start_link(arg1, arg2, arg3)
|
||||
# worker(Pleroma.Worker, [arg1, arg2, arg3]),
|
||||
worker(Cachex, [
|
||||
:user_cache,
|
||||
worker(
|
||||
Cachex,
|
||||
[
|
||||
default_ttl: 25000,
|
||||
ttl_interval: 1000,
|
||||
limit: 2500
|
||||
]
|
||||
]),
|
||||
:user_cache,
|
||||
[
|
||||
default_ttl: 25000,
|
||||
ttl_interval: 1000,
|
||||
limit: 2500
|
||||
]
|
||||
],
|
||||
id: :cachex_user
|
||||
),
|
||||
worker(
|
||||
Cachex,
|
||||
[
|
||||
:object_cache,
|
||||
[
|
||||
default_ttl: 25000,
|
||||
ttl_interval: 1000,
|
||||
limit: 2500
|
||||
]
|
||||
],
|
||||
id: :cachex_object
|
||||
),
|
||||
worker(
|
||||
Cachex,
|
||||
[
|
||||
|
|
@ -40,8 +57,8 @@ defmodule Pleroma.Application do
|
|||
id: :cachex_idem
|
||||
),
|
||||
worker(Pleroma.Web.Federator, []),
|
||||
worker(Pleroma.Gopher.Server, []),
|
||||
worker(Pleroma.Stats, [])
|
||||
worker(Pleroma.Stats, []),
|
||||
worker(Pleroma.Gopher.Server, [])
|
||||
] ++
|
||||
if Mix.env() == :test,
|
||||
do: [],
|
||||
|
|
|
|||
42
lib/pleroma/config.ex
Normal file
42
lib/pleroma/config.ex
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
defmodule Pleroma.Config do
|
||||
defmodule Error do
|
||||
defexception [:message]
|
||||
end
|
||||
|
||||
def get(key), do: get(key, nil)
|
||||
|
||||
def get([key], default), do: get(key, default)
|
||||
|
||||
def get([parent_key | keys], default) do
|
||||
Application.get_env(:pleroma, parent_key)
|
||||
|> get_in(keys) || default
|
||||
end
|
||||
|
||||
def get(key, default) do
|
||||
Application.get_env(:pleroma, key, default)
|
||||
end
|
||||
|
||||
def get!(key) do
|
||||
value = get(key, nil)
|
||||
|
||||
if value == nil do
|
||||
raise(Error, message: "Missing configuration value: #{inspect(key)}")
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def put([key], value), do: put(key, value)
|
||||
|
||||
def put([parent_key | keys], value) do
|
||||
parent =
|
||||
Application.get_env(:pleroma, parent_key)
|
||||
|> put_in(keys, value)
|
||||
|
||||
Application.put_env(:pleroma, parent_key, parent)
|
||||
end
|
||||
|
||||
def put(key, value) do
|
||||
Application.put_env(:pleroma, key, value)
|
||||
end
|
||||
end
|
||||
194
lib/pleroma/emoji.ex
Normal file
194
lib/pleroma/emoji.ex
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
defmodule Pleroma.Emoji do
|
||||
@moduledoc """
|
||||
The emojis are loaded from:
|
||||
|
||||
* the built-in Finmojis (if enabled in configuration),
|
||||
* the files: `config/emoji.txt` and `config/custom_emoji.txt`
|
||||
* glob paths
|
||||
|
||||
This GenServer stores in an ETS table the list of the loaded emojis, and also allows to reload the list at runtime.
|
||||
"""
|
||||
use GenServer
|
||||
@ets __MODULE__.Ets
|
||||
@ets_options [:set, :protected, :named_table, {:read_concurrency, true}]
|
||||
|
||||
@doc false
|
||||
def start_link() do
|
||||
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
||||
end
|
||||
|
||||
@doc "Reloads the emojis from disk."
|
||||
@spec reload() :: :ok
|
||||
def reload() do
|
||||
GenServer.call(__MODULE__, :reload)
|
||||
end
|
||||
|
||||
@doc "Returns the path of the emoji `name`."
|
||||
@spec get(String.t()) :: String.t() | nil
|
||||
def get(name) do
|
||||
case :ets.lookup(@ets, name) do
|
||||
[{_, path}] -> path
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Returns all the emojos!!"
|
||||
@spec get_all() :: [{String.t(), String.t()}, ...]
|
||||
def get_all() do
|
||||
:ets.tab2list(@ets)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def init(_) do
|
||||
@ets = :ets.new(@ets, @ets_options)
|
||||
GenServer.cast(self(), :reload)
|
||||
{:ok, nil}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_cast(:reload, state) do
|
||||
load()
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(:reload, _from, state) do
|
||||
load()
|
||||
{:reply, :ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_, _) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old_vsn, state, _extra) do
|
||||
load()
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defp load() do
|
||||
emojis =
|
||||
(load_finmoji(Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)) ++
|
||||
load_from_file("config/emoji.txt") ++
|
||||
load_from_file("config/custom_emoji.txt") ++
|
||||
load_from_globs(
|
||||
Keyword.get(Application.get_env(:pleroma, :emoji, []), :shortcode_globs, [])
|
||||
))
|
||||
|> Enum.reject(fn value -> value == nil end)
|
||||
|
||||
true = :ets.insert(@ets, emojis)
|
||||
:ok
|
||||
end
|
||||
|
||||
@finmoji [
|
||||
"a_trusted_friend",
|
||||
"alandislands",
|
||||
"association",
|
||||
"auroraborealis",
|
||||
"baby_in_a_box",
|
||||
"bear",
|
||||
"black_gold",
|
||||
"christmasparty",
|
||||
"crosscountryskiing",
|
||||
"cupofcoffee",
|
||||
"education",
|
||||
"fashionista_finns",
|
||||
"finnishlove",
|
||||
"flag",
|
||||
"forest",
|
||||
"four_seasons_of_bbq",
|
||||
"girlpower",
|
||||
"handshake",
|
||||
"happiness",
|
||||
"headbanger",
|
||||
"icebreaker",
|
||||
"iceman",
|
||||
"joulutorttu",
|
||||
"kaamos",
|
||||
"kalsarikannit_f",
|
||||
"kalsarikannit_m",
|
||||
"karjalanpiirakka",
|
||||
"kicksled",
|
||||
"kokko",
|
||||
"lavatanssit",
|
||||
"losthopes_f",
|
||||
"losthopes_m",
|
||||
"mattinykanen",
|
||||
"meanwhileinfinland",
|
||||
"moominmamma",
|
||||
"nordicfamily",
|
||||
"out_of_office",
|
||||
"peacemaker",
|
||||
"perkele",
|
||||
"pesapallo",
|
||||
"polarbear",
|
||||
"pusa_hispida_saimensis",
|
||||
"reindeer",
|
||||
"sami",
|
||||
"sauna_f",
|
||||
"sauna_m",
|
||||
"sauna_whisk",
|
||||
"sisu",
|
||||
"stuck",
|
||||
"suomimainittu",
|
||||
"superfood",
|
||||
"swan",
|
||||
"the_cap",
|
||||
"the_conductor",
|
||||
"the_king",
|
||||
"the_voice",
|
||||
"theoriginalsanta",
|
||||
"tomoffinland",
|
||||
"torillatavataan",
|
||||
"unbreakable",
|
||||
"waiting",
|
||||
"white_nights",
|
||||
"woollysocks"
|
||||
]
|
||||
defp load_finmoji(true) do
|
||||
Enum.map(@finmoji, fn finmoji ->
|
||||
{finmoji, "/finmoji/128px/#{finmoji}-128.png"}
|
||||
end)
|
||||
end
|
||||
|
||||
defp load_finmoji(_), do: []
|
||||
|
||||
defp load_from_file(file) do
|
||||
if File.exists?(file) do
|
||||
load_from_file_stream(File.stream!(file))
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp load_from_file_stream(stream) do
|
||||
stream
|
||||
|> Stream.map(&String.strip/1)
|
||||
|> Stream.map(fn line ->
|
||||
case String.split(line, ~r/,\s*/) do
|
||||
[name, file] -> {name, file}
|
||||
_ -> nil
|
||||
end
|
||||
end)
|
||||
|> Enum.to_list()
|
||||
end
|
||||
|
||||
defp load_from_globs(globs) do
|
||||
static_path = Path.join(:code.priv_dir(:pleroma), "static")
|
||||
|
||||
paths =
|
||||
Enum.map(globs, fn glob ->
|
||||
Path.join(static_path, glob)
|
||||
|> Path.wildcard()
|
||||
end)
|
||||
|> Enum.concat()
|
||||
|
||||
Enum.map(paths, fn path ->
|
||||
shortcode = Path.basename(path, Path.extname(path))
|
||||
external_path = Path.join("/", Path.relative_to(path, static_path))
|
||||
{shortcode, external_path}
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
@ -36,6 +36,34 @@ defmodule Pleroma.Filter do
|
|||
Repo.all(query)
|
||||
end
|
||||
|
||||
def create(%Pleroma.Filter{user_id: user_id, filter_id: nil} = filter) do
|
||||
# If filter_id wasn't given, use the max filter_id for this user plus 1.
|
||||
# XXX This could result in a race condition if a user tries to add two
|
||||
# different filters for their account from two different clients at the
|
||||
# same time, but that should be unlikely.
|
||||
|
||||
max_id_query =
|
||||
from(
|
||||
f in Pleroma.Filter,
|
||||
where: f.user_id == ^user_id,
|
||||
select: max(f.filter_id)
|
||||
)
|
||||
|
||||
filter_id =
|
||||
case Repo.one(max_id_query) do
|
||||
# Start allocating from 1
|
||||
nil ->
|
||||
1
|
||||
|
||||
max_id ->
|
||||
max_id + 1
|
||||
end
|
||||
|
||||
filter
|
||||
|> Map.put(:filter_id, filter_id)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
def create(%Pleroma.Filter{} = filter) do
|
||||
Repo.insert(filter)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ defmodule Pleroma.Formatter do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.MediaProxy
|
||||
alias Pleroma.HTML
|
||||
alias Pleroma.Emoji
|
||||
|
||||
@tag_regex ~r/\#\w+/u
|
||||
def parse_tags(text, data \\ %{}) do
|
||||
|
|
@ -28,119 +29,10 @@ defmodule Pleroma.Formatter do
|
|||
|> Enum.filter(fn {_match, user} -> user end)
|
||||
end
|
||||
|
||||
@finmoji [
|
||||
"a_trusted_friend",
|
||||
"alandislands",
|
||||
"association",
|
||||
"auroraborealis",
|
||||
"baby_in_a_box",
|
||||
"bear",
|
||||
"black_gold",
|
||||
"christmasparty",
|
||||
"crosscountryskiing",
|
||||
"cupofcoffee",
|
||||
"education",
|
||||
"fashionista_finns",
|
||||
"finnishlove",
|
||||
"flag",
|
||||
"forest",
|
||||
"four_seasons_of_bbq",
|
||||
"girlpower",
|
||||
"handshake",
|
||||
"happiness",
|
||||
"headbanger",
|
||||
"icebreaker",
|
||||
"iceman",
|
||||
"joulutorttu",
|
||||
"kaamos",
|
||||
"kalsarikannit_f",
|
||||
"kalsarikannit_m",
|
||||
"karjalanpiirakka",
|
||||
"kicksled",
|
||||
"kokko",
|
||||
"lavatanssit",
|
||||
"losthopes_f",
|
||||
"losthopes_m",
|
||||
"mattinykanen",
|
||||
"meanwhileinfinland",
|
||||
"moominmamma",
|
||||
"nordicfamily",
|
||||
"out_of_office",
|
||||
"peacemaker",
|
||||
"perkele",
|
||||
"pesapallo",
|
||||
"polarbear",
|
||||
"pusa_hispida_saimensis",
|
||||
"reindeer",
|
||||
"sami",
|
||||
"sauna_f",
|
||||
"sauna_m",
|
||||
"sauna_whisk",
|
||||
"sisu",
|
||||
"stuck",
|
||||
"suomimainittu",
|
||||
"superfood",
|
||||
"swan",
|
||||
"the_cap",
|
||||
"the_conductor",
|
||||
"the_king",
|
||||
"the_voice",
|
||||
"theoriginalsanta",
|
||||
"tomoffinland",
|
||||
"torillatavataan",
|
||||
"unbreakable",
|
||||
"waiting",
|
||||
"white_nights",
|
||||
"woollysocks"
|
||||
]
|
||||
def emojify(text) do
|
||||
emojify(text, Emoji.get_all())
|
||||
end
|
||||
|
||||
@finmoji_with_filenames Enum.map(@finmoji, fn finmoji ->
|
||||
{finmoji, "/finmoji/128px/#{finmoji}-128.png"}
|
||||
end)
|
||||
|
||||
@emoji_from_file (with {:ok, default} <- File.read("config/emoji.txt") do
|
||||
custom =
|
||||
with {:ok, custom} <- File.read("config/custom_emoji.txt") do
|
||||
custom
|
||||
else
|
||||
_e -> ""
|
||||
end
|
||||
|
||||
(default <> "\n" <> custom)
|
||||
|> String.trim()
|
||||
|> String.split(~r/\n+/)
|
||||
|> Enum.map(fn line ->
|
||||
[name, file] = String.split(line, ~r/,\s*/)
|
||||
{name, file}
|
||||
end)
|
||||
else
|
||||
_ -> []
|
||||
end)
|
||||
|
||||
@emoji_from_globs (
|
||||
static_path = Path.join(:code.priv_dir(:pleroma), "static")
|
||||
|
||||
globs =
|
||||
Application.get_env(:pleroma, :emoji, [])
|
||||
|> Keyword.get(:shortcode_globs, [])
|
||||
|
||||
paths =
|
||||
Enum.map(globs, fn glob ->
|
||||
Path.join(static_path, glob)
|
||||
|> Path.wildcard()
|
||||
end)
|
||||
|> Enum.concat()
|
||||
|
||||
Enum.map(paths, fn path ->
|
||||
shortcode = Path.basename(path, Path.extname(path))
|
||||
external_path = Path.join("/", Path.relative_to(path, static_path))
|
||||
{shortcode, external_path}
|
||||
end)
|
||||
)
|
||||
|
||||
@emoji @finmoji_with_filenames ++ @emoji_from_globs ++ @emoji_from_file
|
||||
|
||||
def emojify(text, emoji \\ @emoji)
|
||||
def emojify(text, nil), do: text
|
||||
|
||||
def emojify(text, emoji) do
|
||||
|
|
@ -160,39 +52,22 @@ defmodule Pleroma.Formatter do
|
|||
end
|
||||
|
||||
def get_emoji(text) when is_binary(text) do
|
||||
Enum.filter(@emoji, fn {emoji, _} -> String.contains?(text, ":#{emoji}:") end)
|
||||
Enum.filter(Emoji.get_all(), fn {emoji, _} -> String.contains?(text, ":#{emoji}:") end)
|
||||
end
|
||||
|
||||
def get_emoji(_), do: []
|
||||
|
||||
def get_custom_emoji() do
|
||||
@emoji
|
||||
end
|
||||
|
||||
@link_regex ~r/[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+/ui
|
||||
|
||||
# IANA got a list https://www.iana.org/assignments/uri-schemes/ but
|
||||
# Stuff like ipfs isn’t in it
|
||||
# There is very niche stuff
|
||||
@uri_schemes [
|
||||
"https://",
|
||||
"http://",
|
||||
"dat://",
|
||||
"dweb://",
|
||||
"gopher://",
|
||||
"ipfs://",
|
||||
"ipns://",
|
||||
"irc:",
|
||||
"ircs:",
|
||||
"magnet:",
|
||||
"mailto:",
|
||||
"mumble:",
|
||||
"ssb://",
|
||||
"xmpp:"
|
||||
]
|
||||
@uri_schemes Application.get_env(:pleroma, :uri_schemes, [])
|
||||
@valid_schemes Keyword.get(@uri_schemes, :valid_schemes, [])
|
||||
|
||||
# TODO: make it use something other than @link_regex
|
||||
def html_escape(text) do
|
||||
def html_escape(text, "text/html") do
|
||||
HTML.filter_tags(text)
|
||||
end
|
||||
|
||||
def html_escape(text, "text/plain") do
|
||||
Regex.split(@link_regex, text, include_captures: true)
|
||||
|> Enum.map_every(2, fn chunk ->
|
||||
{:safe, part} = Phoenix.HTML.html_escape(chunk)
|
||||
|
|
@ -203,14 +78,10 @@ defmodule Pleroma.Formatter do
|
|||
|
||||
@doc "changes scheme:... urls to html links"
|
||||
def add_links({subs, text}) do
|
||||
additionnal_schemes =
|
||||
Application.get_env(:pleroma, :uri_schemes, [])
|
||||
|> Keyword.get(:additionnal_schemes, [])
|
||||
|
||||
links =
|
||||
text
|
||||
|> String.split([" ", "\t", "<br>"])
|
||||
|> Enum.filter(fn word -> String.starts_with?(word, @uri_schemes ++ additionnal_schemes) end)
|
||||
|> Enum.filter(fn word -> String.starts_with?(word, @valid_schemes) end)
|
||||
|> Enum.filter(fn word -> Regex.match?(@link_regex, word) end)
|
||||
|> Enum.map(fn url -> {Ecto.UUID.generate(), url} end)
|
||||
|> Enum.sort_by(fn {_, url} -> -String.length(url) end)
|
||||
|
|
@ -222,13 +93,7 @@ defmodule Pleroma.Formatter do
|
|||
subs =
|
||||
subs ++
|
||||
Enum.map(links, fn {uuid, url} ->
|
||||
{:safe, link} = Phoenix.HTML.Link.link(url, to: url)
|
||||
|
||||
link =
|
||||
link
|
||||
|> IO.iodata_to_binary()
|
||||
|
||||
{uuid, link}
|
||||
{uuid, "<a href=\"#{url}\">#{url}</a>"}
|
||||
end)
|
||||
|
||||
{subs, uuid_text}
|
||||
|
|
@ -250,7 +115,12 @@ defmodule Pleroma.Formatter do
|
|||
subs =
|
||||
subs ++
|
||||
Enum.map(mentions, fn {match, %User{ap_id: ap_id, info: info}, uuid} ->
|
||||
ap_id = info["source_data"]["url"] || ap_id
|
||||
ap_id =
|
||||
if is_binary(info["source_data"]["url"]) do
|
||||
info["source_data"]["url"]
|
||||
else
|
||||
ap_id
|
||||
end
|
||||
|
||||
short_match = String.split(match, "@") |> tl() |> hd()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
defmodule Pleroma.Gopher.Server do
|
||||
use GenServer
|
||||
require Logger
|
||||
@gopher Application.get_env(:pleroma, :gopher)
|
||||
|
||||
def start_link() do
|
||||
ip = Keyword.get(@gopher, :ip, {0, 0, 0, 0})
|
||||
port = Keyword.get(@gopher, :port, 1234)
|
||||
config = Pleroma.Config.get(:gopher, [])
|
||||
ip = Keyword.get(config, :ip, {0, 0, 0, 0})
|
||||
port = Keyword.get(config, :port, 1234)
|
||||
GenServer.start_link(__MODULE__, [ip, port], [])
|
||||
end
|
||||
|
||||
def init([ip, port]) do
|
||||
if Keyword.get(@gopher, :enabled, false) do
|
||||
if Pleroma.Config.get([:gopher, :enabled], false) do
|
||||
Logger.info("Starting gopher server on #{port}")
|
||||
|
||||
:ranch.start_listener(
|
||||
|
|
@ -37,9 +37,6 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
|
|||
alias Pleroma.Repo
|
||||
alias Pleroma.HTML
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@gopher Application.get_env(:pleroma, :gopher)
|
||||
|
||||
def start_link(ref, socket, transport, opts) do
|
||||
pid = spawn_link(__MODULE__, :init, [ref, socket, transport, opts])
|
||||
{:ok, pid}
|
||||
|
|
@ -62,7 +59,7 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
|
|||
|
||||
def link(name, selector, type \\ 1) do
|
||||
address = Pleroma.Web.Endpoint.host()
|
||||
port = Keyword.get(@gopher, :port, 1234)
|
||||
port = Pleroma.Config.get([:gopher, :port], 1234)
|
||||
"#{type}#{name}\t#{selector}\t#{address}\t#{port}\r\n"
|
||||
end
|
||||
|
||||
|
|
@ -85,7 +82,7 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
|
|||
end
|
||||
|
||||
def response("") do
|
||||
info("Welcome to #{Keyword.get(@instance, :name, "Pleroma")}!") <>
|
||||
info("Welcome to #{Pleroma.Config.get([:instance, :name], "Pleroma")}!") <>
|
||||
link("Public Timeline", "/main/public") <>
|
||||
link("Federated Timeline", "/main/all") <> ".\r\n"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
defmodule Pleroma.HTML do
|
||||
alias HtmlSanitizeEx.Scrubber
|
||||
|
||||
@markup Application.get_env(:pleroma, :markup)
|
||||
|
||||
defp get_scrubbers(scrubber) when is_atom(scrubber), do: [scrubber]
|
||||
defp get_scrubbers(scrubbers) when is_list(scrubbers), do: scrubbers
|
||||
defp get_scrubbers(_), do: [Pleroma.HTML.Scrubber.Default]
|
||||
|
||||
def get_scrubbers() do
|
||||
Keyword.get(@markup, :scrub_policy)
|
||||
Pleroma.Config.get([:markup, :scrub_policy])
|
||||
|> get_scrubbers
|
||||
end
|
||||
|
||||
|
|
@ -36,11 +34,13 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
|
|||
paragraphs, breaks and links are allowed through the filter.
|
||||
"""
|
||||
|
||||
@markup Application.get_env(:pleroma, :markup)
|
||||
@uri_schemes Application.get_env(:pleroma, :uri_schemes, [])
|
||||
@valid_schemes Keyword.get(@uri_schemes, :valid_schemes, [])
|
||||
|
||||
require HtmlSanitizeEx.Scrubber.Meta
|
||||
alias HtmlSanitizeEx.Scrubber.Meta
|
||||
|
||||
@valid_schemes ["http", "https"]
|
||||
|
||||
Meta.remove_cdata_sections_before_scrub()
|
||||
Meta.strip_comments()
|
||||
|
||||
|
|
@ -56,11 +56,11 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
|
|||
Meta.allow_tag_with_these_attributes("span", [])
|
||||
|
||||
# allow inline images for custom emoji
|
||||
@markup Application.get_env(:pleroma, :markup)
|
||||
@allow_inline_images Keyword.get(@markup, :allow_inline_images)
|
||||
|
||||
if @allow_inline_images do
|
||||
Meta.allow_tag_with_uri_attributes("img", ["src"], @valid_schemes)
|
||||
# restrict img tags to http/https only, because of MediaProxy.
|
||||
Meta.allow_tag_with_uri_attributes("img", ["src"], ["http", "https"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes("img", [
|
||||
"width",
|
||||
|
|
@ -79,7 +79,9 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
|||
require HtmlSanitizeEx.Scrubber.Meta
|
||||
alias HtmlSanitizeEx.Scrubber.Meta
|
||||
|
||||
@valid_schemes ["http", "https"]
|
||||
@markup Application.get_env(:pleroma, :markup)
|
||||
@uri_schemes Application.get_env(:pleroma, :uri_schemes, [])
|
||||
@valid_schemes Keyword.get(@uri_schemes, :valid_schemes, [])
|
||||
|
||||
Meta.remove_cdata_sections_before_scrub()
|
||||
Meta.strip_comments()
|
||||
|
|
@ -87,6 +89,8 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
|||
Meta.allow_tag_with_uri_attributes("a", ["href"], @valid_schemes)
|
||||
Meta.allow_tag_with_these_attributes("a", ["name", "title"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes("abbr", ["title"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes("b", [])
|
||||
Meta.allow_tag_with_these_attributes("blockquote", [])
|
||||
Meta.allow_tag_with_these_attributes("br", [])
|
||||
|
|
@ -103,11 +107,11 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
|||
Meta.allow_tag_with_these_attributes("u", [])
|
||||
Meta.allow_tag_with_these_attributes("ul", [])
|
||||
|
||||
@markup Application.get_env(:pleroma, :markup)
|
||||
@allow_inline_images Keyword.get(@markup, :allow_inline_images)
|
||||
|
||||
if @allow_inline_images do
|
||||
Meta.allow_tag_with_uri_attributes("img", ["src"], @valid_schemes)
|
||||
# restrict img tags to http/https only, because of MediaProxy.
|
||||
Meta.allow_tag_with_uri_attributes("img", ["src"], ["http", "https"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes("img", [
|
||||
"width",
|
||||
|
|
@ -173,6 +177,8 @@ defmodule Pleroma.HTML.Transform.MediaProxy do
|
|||
{"img", attributes, children}
|
||||
end
|
||||
|
||||
def scrub({:comment, children}), do: ""
|
||||
|
||||
def scrub({tag, attributes, children}), do: {tag, attributes, children}
|
||||
def scrub({tag, children}), do: children
|
||||
def scrub(text), do: text
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ defmodule Pleroma.HTTP do
|
|||
def process_request_options(options) do
|
||||
config = Application.get_env(:pleroma, :http, [])
|
||||
proxy = Keyword.get(config, :proxy_url, nil)
|
||||
options = options ++ [hackney: [pool: :default]]
|
||||
|
||||
case proxy do
|
||||
nil -> options
|
||||
|
|
|
|||
|
|
@ -69,6 +69,25 @@ defmodule Pleroma.List do
|
|||
Repo.all(query)
|
||||
end
|
||||
|
||||
# Get lists to which the account belongs.
|
||||
def get_lists_account_belongs(%User{} = owner, account_id) do
|
||||
user = Repo.get(User, account_id)
|
||||
|
||||
query =
|
||||
from(
|
||||
l in Pleroma.List,
|
||||
where:
|
||||
l.user_id == ^owner.id and
|
||||
fragment(
|
||||
"? = ANY(?)",
|
||||
^user.follower_address,
|
||||
l.following
|
||||
)
|
||||
)
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
def rename(%Pleroma.List{} = list, title) do
|
||||
list
|
||||
|> title_changeset(%{title: title})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
defmodule Pleroma.Notification do
|
||||
use Ecto.Schema
|
||||
alias Pleroma.{User, Activity, Notification, Repo}
|
||||
alias Pleroma.{User, Activity, Notification, Repo, Object}
|
||||
import Ecto.Query
|
||||
|
||||
schema "notifications" do
|
||||
|
|
@ -42,6 +42,20 @@ defmodule Pleroma.Notification do
|
|||
Repo.all(query)
|
||||
end
|
||||
|
||||
def set_read_up_to(%{id: user_id} = _user, id) do
|
||||
query =
|
||||
from(
|
||||
n in Notification,
|
||||
where: n.user_id == ^user_id,
|
||||
where: n.id <= ^id,
|
||||
update: [
|
||||
set: [seen: true]
|
||||
]
|
||||
)
|
||||
|
||||
Repo.update_all(query, [])
|
||||
end
|
||||
|
||||
def get(%{id: user_id} = _user, id) do
|
||||
query =
|
||||
from(
|
||||
|
|
@ -81,7 +95,7 @@ defmodule Pleroma.Notification do
|
|||
|
||||
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)
|
||||
users = get_notified_from_activity(activity)
|
||||
|
||||
notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
|
||||
{:ok, notifications}
|
||||
|
|
@ -99,4 +113,64 @@ defmodule Pleroma.Notification do
|
|||
notification
|
||||
end
|
||||
end
|
||||
|
||||
def get_notified_from_activity(activity, local_only \\ true)
|
||||
|
||||
def get_notified_from_activity(
|
||||
%Activity{data: %{"to" => _, "type" => type} = data} = activity,
|
||||
local_only
|
||||
)
|
||||
when type in ["Create", "Like", "Announce", "Follow"] do
|
||||
recipients =
|
||||
[]
|
||||
|> maybe_notify_to_recipients(activity)
|
||||
|> maybe_notify_mentioned_recipients(activity)
|
||||
|> Enum.uniq()
|
||||
|
||||
User.get_users_from_set(recipients, local_only)
|
||||
end
|
||||
|
||||
def get_notified_from_activity(_, local_only), do: []
|
||||
|
||||
defp maybe_notify_to_recipients(
|
||||
recipients,
|
||||
%Activity{data: %{"to" => to, "type" => type}} = activity
|
||||
) do
|
||||
recipients ++ to
|
||||
end
|
||||
|
||||
defp maybe_notify_mentioned_recipients(
|
||||
recipients,
|
||||
%Activity{data: %{"to" => to, "type" => type} = data} = activity
|
||||
)
|
||||
when type == "Create" do
|
||||
object = Object.normalize(data["object"])
|
||||
|
||||
object_data =
|
||||
cond do
|
||||
!is_nil(object) ->
|
||||
object.data
|
||||
|
||||
is_map(data["object"]) ->
|
||||
data["object"]
|
||||
|
||||
true ->
|
||||
%{}
|
||||
end
|
||||
|
||||
tagged_mentions = maybe_extract_mentions(object_data)
|
||||
|
||||
recipients ++ tagged_mentions
|
||||
end
|
||||
|
||||
defp maybe_notify_mentioned_recipients(recipients, _), do: recipients
|
||||
|
||||
defp maybe_extract_mentions(%{"tag" => tag}) do
|
||||
tag
|
||||
|> Enum.filter(fn x -> is_map(x) end)
|
||||
|> Enum.filter(fn x -> x["type"] == "Mention" end)
|
||||
|> Enum.map(fn x -> x["href"] end)
|
||||
end
|
||||
|
||||
defp maybe_extract_mentions(_), do: []
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
defmodule Pleroma.Object do
|
||||
use Ecto.Schema
|
||||
alias Pleroma.{Repo, Object}
|
||||
alias Pleroma.{Repo, Object, Activity}
|
||||
import Ecto.{Query, Changeset}
|
||||
|
||||
schema "objects" do
|
||||
|
|
@ -37,7 +37,7 @@ defmodule Pleroma.Object do
|
|||
else
|
||||
key = "object:#{ap_id}"
|
||||
|
||||
Cachex.fetch!(:user_cache, key, fn _ ->
|
||||
Cachex.fetch!(:object_cache, key, fn _ ->
|
||||
object = get_by_ap_id(ap_id)
|
||||
|
||||
if object do
|
||||
|
|
@ -52,4 +52,12 @@ defmodule Pleroma.Object do
|
|||
def context_mapping(context) do
|
||||
Object.change(%Object{}, %{data: %{"id" => context}})
|
||||
end
|
||||
|
||||
def delete(%Object{data: %{"id" => id}} = object) do
|
||||
with Repo.delete(object),
|
||||
Repo.delete_all(Activity.all_non_create_by_object_ap_id_q(id)),
|
||||
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do
|
||||
{:ok, object}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
18
lib/pleroma/plugs/federating_plug.ex
Normal file
18
lib/pleroma/plugs/federating_plug.ex
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
defmodule Pleroma.Web.FederatingPlug do
|
||||
import Plug.Conn
|
||||
|
||||
def init(options) do
|
||||
options
|
||||
end
|
||||
|
||||
def call(conn, opts) do
|
||||
if Keyword.get(Application.get_env(:pleroma, :instance), :federating) do
|
||||
conn
|
||||
else
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> Phoenix.Controller.render(Pleroma.Web.ErrorView, "404.json")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,64 +1,74 @@
|
|||
defmodule Pleroma.Upload do
|
||||
alias Ecto.UUID
|
||||
|
||||
@storage_backend Application.get_env(:pleroma, Pleroma.Upload)
|
||||
|> Keyword.fetch!(:uploader)
|
||||
def check_file_size(path, nil), do: true
|
||||
|
||||
def store(%Plug.Upload{} = file, should_dedupe) do
|
||||
content_type = get_content_type(file.path)
|
||||
|
||||
uuid = get_uuid(file, should_dedupe)
|
||||
name = get_name(file, uuid, content_type, should_dedupe)
|
||||
|
||||
strip_exif_data(content_type, file.path)
|
||||
|
||||
{:ok, url_path} =
|
||||
@storage_backend.put_file(name, uuid, file.path, content_type, should_dedupe)
|
||||
|
||||
%{
|
||||
"type" => "Document",
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => content_type,
|
||||
"href" => url_path
|
||||
}
|
||||
],
|
||||
"name" => name
|
||||
}
|
||||
def check_file_size(path, size_limit) do
|
||||
{:ok, %{size: size}} = File.stat(path)
|
||||
size <= size_limit
|
||||
end
|
||||
|
||||
def store(%{"img" => "data:image/" <> image_data}, should_dedupe) do
|
||||
def store(file, should_dedupe, size_limit \\ nil)
|
||||
|
||||
def store(%Plug.Upload{} = file, should_dedupe, size_limit) do
|
||||
content_type = get_content_type(file.path)
|
||||
|
||||
with uuid <- get_uuid(file, should_dedupe),
|
||||
name <- get_name(file, uuid, content_type, should_dedupe),
|
||||
true <- check_file_size(file.path, size_limit) do
|
||||
strip_exif_data(content_type, file.path)
|
||||
|
||||
{:ok, url_path} = uploader().put_file(name, uuid, file.path, content_type, should_dedupe)
|
||||
|
||||
%{
|
||||
"type" => "Document",
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => content_type,
|
||||
"href" => url_path
|
||||
}
|
||||
],
|
||||
"name" => name
|
||||
}
|
||||
else
|
||||
_e -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def store(%{"img" => "data:image/" <> image_data}, should_dedupe, size_limit) do
|
||||
parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data)
|
||||
data = Base.decode64!(parsed["data"], ignore: :whitespace)
|
||||
|
||||
tmp_path = tempfile_for_image(data)
|
||||
with tmp_path <- tempfile_for_image(data),
|
||||
uuid <- UUID.generate(),
|
||||
true <- check_file_size(tmp_path, size_limit) do
|
||||
content_type = get_content_type(tmp_path)
|
||||
strip_exif_data(content_type, tmp_path)
|
||||
|
||||
uuid = UUID.generate()
|
||||
name =
|
||||
create_name(
|
||||
String.downcase(Base.encode16(:crypto.hash(:sha256, data))),
|
||||
parsed["filetype"],
|
||||
content_type
|
||||
)
|
||||
|
||||
content_type = get_content_type(tmp_path)
|
||||
strip_exif_data(content_type, tmp_path)
|
||||
{:ok, url_path} = uploader().put_file(name, uuid, tmp_path, content_type, should_dedupe)
|
||||
|
||||
name =
|
||||
create_name(
|
||||
String.downcase(Base.encode16(:crypto.hash(:sha256, data))),
|
||||
parsed["filetype"],
|
||||
content_type
|
||||
)
|
||||
|
||||
{:ok, url_path} = @storage_backend.put_file(name, uuid, tmp_path, content_type, should_dedupe)
|
||||
|
||||
%{
|
||||
"type" => "Image",
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => content_type,
|
||||
"href" => url_path
|
||||
}
|
||||
],
|
||||
"name" => name
|
||||
}
|
||||
%{
|
||||
"type" => "Image",
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => content_type,
|
||||
"href" => url_path
|
||||
}
|
||||
],
|
||||
"name" => name
|
||||
}
|
||||
else
|
||||
_e -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
@ -167,4 +177,8 @@ defmodule Pleroma.Upload do
|
|||
_e -> "application/octet-stream"
|
||||
end
|
||||
end
|
||||
|
||||
defp uploader() do
|
||||
Pleroma.Config.get!([Pleroma.Upload, :uploader])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
defmodule Pleroma.Uploaders.S3 do
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
@behaviour Pleroma.Uploaders.Uploader
|
||||
|
||||
def put_file(name, uuid, path, content_type, _should_dedupe) do
|
||||
settings = Application.get_env(:pleroma, Pleroma.Uploaders.S3)
|
||||
bucket = Keyword.fetch!(settings, :bucket)
|
||||
public_endpoint = Keyword.fetch!(settings, :public_endpoint)
|
||||
force_media_proxy = Keyword.fetch!(settings, :force_media_proxy)
|
||||
|
||||
{:ok, file_data} = File.read(path)
|
||||
|
||||
|
|
@ -19,7 +22,16 @@ defmodule Pleroma.Uploaders.S3 do
|
|||
])
|
||||
|> ExAws.request()
|
||||
|
||||
{:ok, "#{public_endpoint}/#{bucket}/#{s3_name}"}
|
||||
url_base = "#{public_endpoint}/#{bucket}/#{s3_name}"
|
||||
|
||||
public_url =
|
||||
if force_media_proxy do
|
||||
MediaProxy.url(url_base)
|
||||
else
|
||||
url_base
|
||||
end
|
||||
|
||||
{:ok, public_url}
|
||||
end
|
||||
|
||||
defp encode(name) do
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
defmodule Pleroma.Uploaders.Swift.Keystone do
|
||||
use HTTPoison.Base
|
||||
|
||||
@settings Application.get_env(:pleroma, Pleroma.Uploaders.Swift)
|
||||
|
||||
def process_url(url) do
|
||||
Enum.join(
|
||||
[Keyword.fetch!(@settings, :auth_url), url],
|
||||
[Pleroma.Config.get!([Pleroma.Uploaders.Swift, :auth_url]), url],
|
||||
"/"
|
||||
)
|
||||
end
|
||||
|
|
@ -16,9 +14,10 @@ defmodule Pleroma.Uploaders.Swift.Keystone do
|
|||
end
|
||||
|
||||
def get_token() do
|
||||
username = Keyword.fetch!(@settings, :username)
|
||||
password = Keyword.fetch!(@settings, :password)
|
||||
tenant_id = Keyword.fetch!(@settings, :tenant_id)
|
||||
settings = Pleroma.Config.get(Pleroma.Uploaders.Swift)
|
||||
username = Keyword.fetch!(settings, :username)
|
||||
password = Keyword.fetch!(settings, :password)
|
||||
tenant_id = Keyword.fetch!(settings, :tenant_id)
|
||||
|
||||
case post(
|
||||
"/tokens",
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
defmodule Pleroma.Uploaders.Swift.Client do
|
||||
use HTTPoison.Base
|
||||
|
||||
@settings Application.get_env(:pleroma, Pleroma.Uploaders.Swift)
|
||||
|
||||
def process_url(url) do
|
||||
Enum.join(
|
||||
[Keyword.fetch!(@settings, :storage_url), url],
|
||||
[Pleroma.Config.get!([Pleroma.Uploaders.Swift, :storage_url]), url],
|
||||
"/"
|
||||
)
|
||||
end
|
||||
|
||||
def upload_file(filename, body, content_type) do
|
||||
object_url = Keyword.fetch!(@settings, :object_url)
|
||||
object_url = Pleroma.Config.get!([Pleroma.Uploaders.Swift, :object_url])
|
||||
token = Pleroma.Uploaders.Swift.Keystone.get_token()
|
||||
|
||||
case put("#{filename}", body, "X-Auth-Token": token, "Content-Type": content_type) do
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ defmodule Pleroma.User do
|
|||
import Ecto.{Changeset, Query}
|
||||
alias Pleroma.{Repo, User, Object, Web, Activity, Notification}
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Pleroma.Web.{OStatus, Websub}
|
||||
alias Pleroma.Web.{OStatus, Websub, OAuth}
|
||||
alias Pleroma.Web.ActivityPub.{Utils, ActivityPub}
|
||||
|
||||
schema "users" do
|
||||
|
|
@ -42,6 +42,10 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def profile_url(%User{info: %{"source_data" => %{"url" => url}}}), do: url
|
||||
def profile_url(%User{ap_id: ap_id}), do: ap_id
|
||||
def profile_url(_), do: nil
|
||||
|
||||
def ap_id(%User{nickname: nickname}) do
|
||||
"#{Web.base_url()}/users/#{nickname}"
|
||||
end
|
||||
|
|
@ -132,6 +136,9 @@ defmodule Pleroma.User do
|
|||
|> validate_required([:password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|
||||
OAuth.Token.delete_user_tokens(struct)
|
||||
OAuth.Authorization.delete_user_authorizations(struct)
|
||||
|
||||
if changeset.valid? do
|
||||
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
||||
|
||||
|
|
@ -184,33 +191,16 @@ defmodule Pleroma.User do
|
|||
|
||||
def needs_update?(_), do: true
|
||||
|
||||
def maybe_direct_follow(%User{} = follower, %User{info: info} = followed) do
|
||||
user_config = Application.get_env(:pleroma, :user)
|
||||
deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked)
|
||||
def maybe_direct_follow(%User{} = follower, %User{local: true, info: %{"locked" => true}}) do
|
||||
{:ok, follower}
|
||||
end
|
||||
|
||||
user_info = user_info(followed)
|
||||
def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
|
||||
follow(follower, followed)
|
||||
end
|
||||
|
||||
should_direct_follow =
|
||||
cond do
|
||||
# if the account is locked, don't pre-create the relationship
|
||||
user_info[:locked] == true ->
|
||||
false
|
||||
|
||||
# if the users are blocking each other, we shouldn't even be here, but check for it anyway
|
||||
deny_follow_blocked and
|
||||
(User.blocks?(follower, followed) or User.blocks?(followed, follower)) ->
|
||||
false
|
||||
|
||||
# if OStatus, then there is no three-way handshake to follow
|
||||
User.ap_enabled?(followed) != true ->
|
||||
true
|
||||
|
||||
# if there are no other reasons not to, just pre-create the relationship
|
||||
true ->
|
||||
true
|
||||
end
|
||||
|
||||
if should_direct_follow do
|
||||
def maybe_direct_follow(%User{} = follower, %User{} = followed) do
|
||||
if !User.ap_enabled?(followed) do
|
||||
follow(follower, followed)
|
||||
else
|
||||
{:ok, follower}
|
||||
|
|
@ -305,6 +295,7 @@ defmodule Pleroma.User do
|
|||
def invalidate_cache(user) do
|
||||
Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
|
||||
Cachex.del(:user_cache, "nickname:#{user.nickname}")
|
||||
Cachex.del(:user_cache, "user_info:#{user.id}")
|
||||
end
|
||||
|
||||
def get_cached_by_ap_id(ap_id) do
|
||||
|
|
@ -473,36 +464,25 @@ defmodule Pleroma.User do
|
|||
update_and_set_cache(cs)
|
||||
end
|
||||
|
||||
def get_notified_from_activity_query(to) do
|
||||
def get_users_from_set_query(ap_ids, false) do
|
||||
from(
|
||||
u in User,
|
||||
where: u.ap_id in ^to,
|
||||
where: u.ap_id in ^ap_ids
|
||||
)
|
||||
end
|
||||
|
||||
def get_users_from_set_query(ap_ids, true) do
|
||||
query = get_users_from_set_query(ap_ids, false)
|
||||
|
||||
from(
|
||||
u in query,
|
||||
where: u.local == true
|
||||
)
|
||||
end
|
||||
|
||||
def get_notified_from_activity(%Activity{recipients: to, data: %{"type" => "Announce"} = data}) do
|
||||
object = Object.normalize(data["object"])
|
||||
actor = User.get_cached_by_ap_id(data["actor"])
|
||||
|
||||
# ensure that the actor who published the announced object appears only once
|
||||
to =
|
||||
if actor.nickname != nil do
|
||||
to ++ [object.data["actor"]]
|
||||
else
|
||||
to
|
||||
end
|
||||
|> Enum.uniq()
|
||||
|
||||
query = get_notified_from_activity_query(to)
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
def get_notified_from_activity(%Activity{recipients: to}) do
|
||||
query = get_notified_from_activity_query(to)
|
||||
|
||||
Repo.all(query)
|
||||
def get_users_from_set(ap_ids, local_only \\ true) do
|
||||
get_users_from_set_query(ap_ids, local_only)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def get_recipients_from_activity(%Activity{recipients: to}) do
|
||||
|
|
@ -632,8 +612,8 @@ defmodule Pleroma.User do
|
|||
)
|
||||
end
|
||||
|
||||
def deactivate(%User{} = user) do
|
||||
new_info = Map.put(user.info, "deactivated", true)
|
||||
def deactivate(%User{} = user, status \\ true) do
|
||||
new_info = Map.put(user.info, "deactivated", status)
|
||||
cs = User.info_changeset(user, %{info: new_info})
|
||||
update_and_set_cache(cs)
|
||||
end
|
||||
|
|
@ -666,7 +646,7 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end)
|
||||
|
||||
:ok
|
||||
{:ok, user}
|
||||
end
|
||||
|
||||
def html_filter_policy(%User{info: %{"no_rich_text" => true}}) do
|
||||
|
|
@ -753,6 +733,7 @@ defmodule Pleroma.User do
|
|||
Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
|
||||
end
|
||||
|
||||
def ap_enabled?(%User{local: true}), do: true
|
||||
def ap_enabled?(%User{info: info}), do: info["ap_enabled"]
|
||||
def ap_enabled?(_), do: false
|
||||
|
||||
|
|
@ -763,4 +744,28 @@ defmodule Pleroma.User do
|
|||
get_or_fetch_by_nickname(uri_or_nickname)
|
||||
end
|
||||
end
|
||||
|
||||
# wait a period of time and return newest version of the User structs
|
||||
# this is because we have synchronous follow APIs and need to simulate them
|
||||
# with an async handshake
|
||||
def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
|
||||
with %User{} = a <- Repo.get(User, a.id),
|
||||
%User{} = b <- Repo.get(User, b.id) do
|
||||
{:ok, a, b}
|
||||
else
|
||||
_e ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
|
||||
with :ok <- :timer.sleep(timeout),
|
||||
%User{} = a <- Repo.get(User, a.id),
|
||||
%User{} = b <- Repo.get(User, b.id) do
|
||||
{:ok, a, b}
|
||||
else
|
||||
_e ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
|
||||
# For Announce activities, we filter the recipients based on following status for any actors
|
||||
# that match actual users. See issue #164 for more information about why this is necessary.
|
||||
defp get_recipients(%{"type" => "Announce"} = data) do
|
||||
|
|
@ -44,7 +42,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
defp check_actor_is_active(actor) do
|
||||
if not is_nil(actor) do
|
||||
with user <- User.get_cached_by_ap_id(actor),
|
||||
nil <- user.info["deactivated"] do
|
||||
false <- !!user.info["deactivated"] do
|
||||
:ok
|
||||
else
|
||||
_e -> :reject
|
||||
|
|
@ -273,8 +271,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
"to" => [user.follower_address, "https://www.w3.org/ns/activitystreams#Public"]
|
||||
}
|
||||
|
||||
with Repo.delete(object),
|
||||
Repo.delete_all(Activity.all_non_create_by_object_ap_id_q(id)),
|
||||
with {:ok, _} <- Object.delete(object),
|
||||
{:ok, activity} <- insert(data, local),
|
||||
:ok <- maybe_federate(activity),
|
||||
{:ok, _actor} <- User.decrease_note_count(user) do
|
||||
|
|
@ -575,9 +572,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> Enum.reverse()
|
||||
end
|
||||
|
||||
def upload(file) do
|
||||
data = Upload.store(file, Application.get_env(:pleroma, :instance)[:dedupe_media])
|
||||
Repo.insert(%Object{data: data})
|
||||
def upload(file, size_limit \\ nil) do
|
||||
with data <-
|
||||
Upload.store(file, Application.get_env(:pleroma, :instance)[:dedupe_media], size_limit),
|
||||
false <- is_nil(data) do
|
||||
Repo.insert(%Object{data: data})
|
||||
end
|
||||
end
|
||||
|
||||
def user_data_from_user_object(data) do
|
||||
|
|
@ -657,14 +657,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
end
|
||||
|
||||
@quarantined_instances Keyword.get(@instance, :quarantined_instances, [])
|
||||
|
||||
def should_federate?(inbox, public) do
|
||||
if public do
|
||||
true
|
||||
else
|
||||
inbox_info = URI.parse(inbox)
|
||||
inbox_info.host not in @quarantined_instances
|
||||
!Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -683,7 +681,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
(Pleroma.Web.Salmon.remote_users(activity) ++ followers)
|
||||
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|
||||
|> Enum.map(fn %{info: %{"source_data" => data}} ->
|
||||
(data["endpoints"] && data["endpoints"]["sharedInbox"]) || data["inbox"]
|
||||
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
|
||||
end)
|
||||
|> Enum.uniq()
|
||||
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|
||||
|
|
@ -756,6 +754,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
|
||||
{:ok, Object.normalize(activity.data["object"])}
|
||||
else
|
||||
{:error, {:reject, nil}} ->
|
||||
{:reject, nil}
|
||||
|
||||
object = %Object{} ->
|
||||
{:ok, object}
|
||||
|
||||
|
|
@ -784,4 +785,38 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
y = activity.data["to"] ++ (activity.data["cc"] || [])
|
||||
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
|
||||
end
|
||||
|
||||
# guard
|
||||
def entire_thread_visible_for_user?(nil, user), do: false
|
||||
|
||||
# child
|
||||
def entire_thread_visible_for_user?(
|
||||
%Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
|
||||
user
|
||||
)
|
||||
when is_binary(parent_id) do
|
||||
parent = Activity.get_in_reply_to_activity(tail)
|
||||
visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user)
|
||||
end
|
||||
|
||||
# root
|
||||
def entire_thread_visible_for_user?(tail, user), do: visible_for_user?(tail, user)
|
||||
|
||||
# filter out broken threads
|
||||
def contain_broken_threads(%Activity{} = activity, %User{} = user) do
|
||||
entire_thread_visible_for_user?(activity, user)
|
||||
end
|
||||
|
||||
# do post-processing on a specific activity
|
||||
def contain_activity(%Activity{} = activity, %User{} = user) do
|
||||
contain_broken_threads(activity, user)
|
||||
end
|
||||
|
||||
# do post-processing on a timeline
|
||||
def contain_timeline(timeline, user) do
|
||||
timeline
|
||||
|> Enum.filter(fn activity ->
|
||||
contain_activity(activity, user)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,12 +4,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
alias Pleroma.Web.ActivityPub.{ObjectView, UserView}
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.Federator
|
||||
|
||||
require Logger
|
||||
|
||||
action_fallback(:errors)
|
||||
|
||||
plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
|
||||
plug(:relay_active? when action in [:relay])
|
||||
|
||||
def relay_active?(conn, _) do
|
||||
if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do
|
||||
conn
|
||||
else
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json(%{error: "not found"})
|
||||
|> halt
|
||||
end
|
||||
end
|
||||
|
||||
def user(conn, %{"nickname" => nickname}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
|
||||
|
|
@ -87,25 +102,43 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
outbox(conn, %{"nickname" => nickname, "max_id" => nil})
|
||||
end
|
||||
|
||||
# TODO: Ensure that this inbox is a recipient of the message
|
||||
def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||
true <- Utils.recipient_in_message(user.ap_id, params),
|
||||
params <- Utils.maybe_splice_recipient(user.ap_id, params) do
|
||||
Federator.enqueue(:incoming_ap_doc, params)
|
||||
json(conn, "ok")
|
||||
end
|
||||
end
|
||||
|
||||
def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
|
||||
Federator.enqueue(:incoming_ap_doc, params)
|
||||
json(conn, "ok")
|
||||
end
|
||||
|
||||
# only accept relayed Creates
|
||||
def inbox(conn, %{"type" => "Create"} = params) do
|
||||
Logger.info(
|
||||
"Signature missing or not from author, relayed Create message, fetching object from source"
|
||||
)
|
||||
|
||||
ActivityPub.fetch_object_from_id(params["object"]["id"])
|
||||
|
||||
json(conn, "ok")
|
||||
end
|
||||
|
||||
def inbox(conn, params) do
|
||||
headers = Enum.into(conn.req_headers, %{})
|
||||
|
||||
if !String.contains?(headers["signature"] || "", params["actor"]) do
|
||||
Logger.info("Signature not from author, relayed message, fetching from source")
|
||||
ActivityPub.fetch_object_from_id(params["object"]["id"])
|
||||
else
|
||||
Logger.info("Signature error - make sure you are forwarding the HTTP Host header!")
|
||||
Logger.info("Could not validate #{params["actor"]}")
|
||||
if String.contains?(headers["signature"], params["actor"]) do
|
||||
Logger.info(
|
||||
"Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
|
||||
)
|
||||
|
||||
Logger.info(inspect(conn.req_headers))
|
||||
end
|
||||
|
||||
json(conn, "ok")
|
||||
json(conn, "error")
|
||||
end
|
||||
|
||||
def relay(conn, params) do
|
||||
|
|
|
|||
|
|
@ -3,10 +3,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
|
|||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@mrf_normalize_markup Application.get_env(:pleroma, :mrf_normalize_markup)
|
||||
|
||||
def filter(%{"type" => activity_type} = object) when activity_type == "Create" do
|
||||
scrub_policy = Keyword.get(@mrf_normalize_markup, :scrub_policy)
|
||||
scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy])
|
||||
|
||||
child = object["object"]
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
|
|||
alias Pleroma.User
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@mrf_rejectnonpublic Application.get_env(:pleroma, :mrf_rejectnonpublic)
|
||||
@allow_followersonly Keyword.get(@mrf_rejectnonpublic, :allow_followersonly)
|
||||
@allow_direct Keyword.get(@mrf_rejectnonpublic, :allow_direct)
|
||||
|
||||
@impl true
|
||||
def filter(%{"type" => "Create"} = object) do
|
||||
user = User.get_cached_by_ap_id(object["actor"])
|
||||
|
|
@ -20,6 +16,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
|
|||
true -> "direct"
|
||||
end
|
||||
|
||||
policy = Pleroma.Config.get(:mrf_rejectnonpublic)
|
||||
|
||||
case visibility do
|
||||
"public" ->
|
||||
{:ok, object}
|
||||
|
|
@ -28,14 +26,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
|
|||
{:ok, object}
|
||||
|
||||
"followers" ->
|
||||
with true <- @allow_followersonly do
|
||||
with true <- Keyword.get(policy, :allow_followersonly) do
|
||||
{:ok, object}
|
||||
else
|
||||
_e -> {:reject, nil}
|
||||
end
|
||||
|
||||
"direct" ->
|
||||
with true <- @allow_direct do
|
||||
with true <- Keyword.get(policy, :allow_direct) do
|
||||
{:ok, object}
|
||||
else
|
||||
_e -> {:reject, nil}
|
||||
|
|
|
|||
|
|
@ -2,60 +2,76 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
alias Pleroma.User
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@mrf_policy Application.get_env(:pleroma, :mrf_simple)
|
||||
defp check_accept(%{host: actor_host} = _actor_info, object) do
|
||||
accepts = Pleroma.Config.get([:mrf_simple, :accept])
|
||||
|
||||
@accept Keyword.get(@mrf_policy, :accept)
|
||||
defp check_accept(%{host: actor_host} = actor_info, object)
|
||||
when length(@accept) > 0 and not (actor_host in @accept) do
|
||||
{:reject, nil}
|
||||
cond do
|
||||
accepts == [] -> {:ok, object}
|
||||
actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
|
||||
Enum.member?(accepts, actor_host) -> {:ok, object}
|
||||
true -> {:reject, nil}
|
||||
end
|
||||
end
|
||||
|
||||
defp check_accept(actor_info, object), do: {:ok, object}
|
||||
|
||||
@reject Keyword.get(@mrf_policy, :reject)
|
||||
defp check_reject(%{host: actor_host} = actor_info, object) when actor_host in @reject do
|
||||
{:reject, nil}
|
||||
defp check_reject(%{host: actor_host} = _actor_info, object) do
|
||||
if Enum.member?(Pleroma.Config.get([:mrf_simple, :reject]), actor_host) do
|
||||
{:reject, nil}
|
||||
else
|
||||
{:ok, object}
|
||||
end
|
||||
end
|
||||
|
||||
defp check_reject(actor_info, object), do: {:ok, object}
|
||||
defp check_media_removal(
|
||||
%{host: actor_host} = _actor_info,
|
||||
%{"type" => "Create", "object" => %{"attachement" => child_attachment}} = object
|
||||
)
|
||||
when length(child_attachment) > 0 do
|
||||
object =
|
||||
if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_removal]), actor_host) do
|
||||
child_object = Map.delete(object["object"], "attachment")
|
||||
Map.put(object, "object", child_object)
|
||||
else
|
||||
object
|
||||
end
|
||||
|
||||
@media_removal Keyword.get(@mrf_policy, :media_removal)
|
||||
defp check_media_removal(%{host: actor_host} = actor_info, %{"type" => "Create"} = object)
|
||||
when actor_host in @media_removal do
|
||||
child_object = Map.delete(object["object"], "attachment")
|
||||
object = Map.put(object, "object", child_object)
|
||||
{:ok, object}
|
||||
end
|
||||
|
||||
defp check_media_removal(actor_info, object), do: {:ok, object}
|
||||
defp check_media_removal(_actor_info, object), do: {:ok, object}
|
||||
|
||||
@media_nsfw Keyword.get(@mrf_policy, :media_nsfw)
|
||||
defp check_media_nsfw(
|
||||
%{host: actor_host} = actor_info,
|
||||
%{host: actor_host} = _actor_info,
|
||||
%{
|
||||
"type" => "Create",
|
||||
"object" => %{"attachment" => child_attachment} = child_object
|
||||
} = object
|
||||
)
|
||||
when actor_host in @media_nsfw and length(child_attachment) > 0 do
|
||||
tags = (child_object["tag"] || []) ++ ["nsfw"]
|
||||
child_object = Map.put(child_object, "tags", tags)
|
||||
child_object = Map.put(child_object, "sensitive", true)
|
||||
object = Map.put(object, "object", child_object)
|
||||
when length(child_attachment) > 0 do
|
||||
object =
|
||||
if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_nsfw]), actor_host) do
|
||||
tags = (child_object["tag"] || []) ++ ["nsfw"]
|
||||
child_object = Map.put(child_object, "tags", tags)
|
||||
child_object = Map.put(child_object, "sensitive", true)
|
||||
Map.put(object, "object", child_object)
|
||||
else
|
||||
object
|
||||
end
|
||||
|
||||
{:ok, object}
|
||||
end
|
||||
|
||||
defp check_media_nsfw(actor_info, object), do: {:ok, object}
|
||||
defp check_media_nsfw(_actor_info, object), do: {:ok, object}
|
||||
|
||||
@ftl_removal Keyword.get(@mrf_policy, :federated_timeline_removal)
|
||||
defp check_ftl_removal(%{host: actor_host} = actor_info, object)
|
||||
when actor_host in @ftl_removal do
|
||||
user = User.get_by_ap_id(object["actor"])
|
||||
|
||||
# flip to/cc relationship to make the post unlisted
|
||||
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
|
||||
object =
|
||||
if "https://www.w3.org/ns/activitystreams#Public" in object["to"] and
|
||||
user.follower_address in object["cc"] do
|
||||
with true <-
|
||||
Enum.member?(
|
||||
Pleroma.Config.get([:mrf_simple, :federated_timeline_removal]),
|
||||
actor_host
|
||||
),
|
||||
user <- User.get_cached_by_ap_id(object["actor"]),
|
||||
true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"],
|
||||
true <- user.follower_address in object["cc"] do
|
||||
to =
|
||||
List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++
|
||||
[user.follower_address]
|
||||
|
|
@ -68,14 +84,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
|> Map.put("to", to)
|
||||
|> Map.put("cc", cc)
|
||||
else
|
||||
object
|
||||
_ -> object
|
||||
end
|
||||
|
||||
{:ok, object}
|
||||
end
|
||||
|
||||
defp check_ftl_removal(actor_info, object), do: {:ok, object}
|
||||
|
||||
@impl true
|
||||
def filter(object) do
|
||||
actor_info = URI.parse(object["actor"])
|
||||
|
|
|
|||
|
|
@ -21,18 +21,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
if is_binary(Enum.at(actor, 0)) do
|
||||
Enum.at(actor, 0)
|
||||
else
|
||||
Enum.find(actor, fn %{"type" => type} -> type == "Person" end)
|
||||
Enum.find(actor, fn %{"type" => type} -> type in ["Person", "Service", "Application"] end)
|
||||
|> Map.get("id")
|
||||
end
|
||||
end
|
||||
|
||||
def get_actor(%{"actor" => actor}) when is_map(actor) do
|
||||
actor["id"]
|
||||
def get_actor(%{"actor" => %{"id" => id}}) when is_bitstring(id) do
|
||||
id
|
||||
end
|
||||
|
||||
def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor) do
|
||||
get_actor(%{"actor" => actor})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks that an imported AP object's actor matches the domain it came from.
|
||||
"""
|
||||
def contain_origin(id, %{"actor" => nil}), do: :error
|
||||
|
||||
def contain_origin(id, %{"actor" => actor} = params) do
|
||||
id_uri = URI.parse(id)
|
||||
actor_uri = URI.parse(get_actor(params))
|
||||
|
|
@ -51,6 +57,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
object
|
||||
|> fix_actor
|
||||
|> fix_attachments
|
||||
|> fix_url
|
||||
|> fix_context
|
||||
|> fix_in_reply_to
|
||||
|> fix_emoji
|
||||
|
|
@ -96,9 +103,25 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
object
|
||||
end
|
||||
|
||||
def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object)
|
||||
when not is_nil(in_reply_to_id) do
|
||||
case ActivityPub.fetch_object_from_id(in_reply_to_id) do
|
||||
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object)
|
||||
when not is_nil(in_reply_to) do
|
||||
in_reply_to_id =
|
||||
cond do
|
||||
is_bitstring(in_reply_to) ->
|
||||
in_reply_to
|
||||
|
||||
is_map(in_reply_to) && is_bitstring(in_reply_to["id"]) ->
|
||||
in_reply_to["id"]
|
||||
|
||||
is_list(in_reply_to) && is_bitstring(Enum.at(in_reply_to, 0)) ->
|
||||
Enum.at(in_reply_to, 0)
|
||||
|
||||
# Maybe I should output an error too?
|
||||
true ->
|
||||
""
|
||||
end
|
||||
|
||||
case fetch_obj_helper(in_reply_to_id) do
|
||||
{:ok, replied_object} ->
|
||||
with %Activity{} = activity <-
|
||||
Activity.get_create_activity_by_object_ap_id(replied_object.data["id"]) do
|
||||
|
|
@ -110,12 +133,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> Map.put("context", replied_object.data["context"] || object["conversation"])
|
||||
else
|
||||
e ->
|
||||
Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
|
||||
Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
|
||||
object
|
||||
end
|
||||
|
||||
e ->
|
||||
Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
|
||||
Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
|
||||
object
|
||||
end
|
||||
end
|
||||
|
|
@ -130,9 +153,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> Map.put("conversation", context)
|
||||
end
|
||||
|
||||
def fix_attachments(object) do
|
||||
def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
|
||||
attachments =
|
||||
(object["attachment"] || [])
|
||||
attachment
|
||||
|> Enum.map(fn data ->
|
||||
url = [%{"type" => "Link", "mediaType" => data["mediaType"], "href" => data["url"]}]
|
||||
Map.put(data, "url", url)
|
||||
|
|
@ -142,21 +165,41 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> Map.put("attachment", attachments)
|
||||
end
|
||||
|
||||
def fix_emoji(object) do
|
||||
tags = object["tag"] || []
|
||||
def fix_attachments(%{"attachment" => attachment} = object) when is_map(attachment) do
|
||||
Map.put(object, "attachment", [attachment])
|
||||
|> fix_attachments()
|
||||
end
|
||||
|
||||
def fix_attachments(object), do: object
|
||||
|
||||
def fix_url(%{"url" => url} = object) when is_map(url) do
|
||||
object
|
||||
|> Map.put("url", url["href"])
|
||||
end
|
||||
|
||||
def fix_url(%{"url" => url} = object) when is_list(url) do
|
||||
first_element = Enum.at(url, 0)
|
||||
|
||||
url_string =
|
||||
cond do
|
||||
is_bitstring(first_element) -> first_element
|
||||
is_map(first_element) -> first_element["href"] || ""
|
||||
true -> ""
|
||||
end
|
||||
|
||||
object
|
||||
|> Map.put("url", url_string)
|
||||
end
|
||||
|
||||
def fix_url(object), do: object
|
||||
|
||||
def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do
|
||||
emoji = tags |> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end)
|
||||
|
||||
emoji =
|
||||
emoji
|
||||
|> Enum.reduce(%{}, fn data, mapping ->
|
||||
name = data["name"]
|
||||
|
||||
name =
|
||||
if String.starts_with?(name, ":") do
|
||||
name |> String.slice(1..-2)
|
||||
else
|
||||
name
|
||||
end
|
||||
name = String.trim(data["name"], ":")
|
||||
|
||||
mapping |> Map.put(name, data["icon"]["url"])
|
||||
end)
|
||||
|
|
@ -168,18 +211,37 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> Map.put("emoji", emoji)
|
||||
end
|
||||
|
||||
def fix_tag(object) do
|
||||
def fix_emoji(%{"tag" => %{"type" => "Emoji"} = tag} = object) do
|
||||
name = String.trim(tag["name"], ":")
|
||||
emoji = %{name => tag["icon"]["url"]}
|
||||
|
||||
object
|
||||
|> Map.put("emoji", emoji)
|
||||
end
|
||||
|
||||
def fix_emoji(object), do: object
|
||||
|
||||
def fix_tag(%{"tag" => tag} = object) when is_list(tag) do
|
||||
tags =
|
||||
(object["tag"] || [])
|
||||
tag
|
||||
|> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
|
||||
|> Enum.map(fn data -> String.slice(data["name"], 1..-1) end)
|
||||
|
||||
combined = (object["tag"] || []) ++ tags
|
||||
combined = tag ++ tags
|
||||
|
||||
object
|
||||
|> Map.put("tag", combined)
|
||||
end
|
||||
|
||||
def fix_tag(%{"tag" => %{"type" => "Hashtag", "name" => hashtag} = tag} = object) do
|
||||
combined = [tag, String.slice(hashtag, 1..-1)]
|
||||
|
||||
object
|
||||
|> Map.put("tag", combined)
|
||||
end
|
||||
|
||||
def fix_tag(object), do: object
|
||||
|
||||
# content map usually only has one language so this will do for now.
|
||||
def fix_content_map(%{"contentMap" => content_map} = object) do
|
||||
content_groups = Map.to_list(content_map)
|
||||
|
|
@ -201,7 +263,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
# - tags
|
||||
# - emoji
|
||||
def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)
|
||||
when objtype in ["Article", "Note", "Video"] do
|
||||
when objtype in ["Article", "Note", "Video", "Page"] do
|
||||
actor = get_actor(data)
|
||||
|
||||
data =
|
||||
|
|
@ -285,8 +347,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
def handle_incoming(
|
||||
%{"type" => "Accept", "object" => follow_object, "actor" => actor, "id" => id} = data
|
||||
) do
|
||||
with %User{} = followed <- User.get_or_fetch_by_ap_id(actor),
|
||||
with actor <- get_actor(data),
|
||||
%User{} = followed <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
|
||||
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
|
||||
{:ok, activity} <-
|
||||
ActivityPub.accept(%{
|
||||
|
|
@ -309,8 +373,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
def handle_incoming(
|
||||
%{"type" => "Reject", "object" => follow_object, "actor" => actor, "id" => id} = data
|
||||
) do
|
||||
with %User{} = followed <- User.get_or_fetch_by_ap_id(actor),
|
||||
with actor <- get_actor(data),
|
||||
%User{} = followed <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
|
||||
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
|
||||
{:ok, activity} <-
|
||||
ActivityPub.accept(%{
|
||||
|
|
@ -329,11 +395,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = _data
|
||||
%{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = data
|
||||
) do
|
||||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <-
|
||||
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||
with actor <- get_actor(data),
|
||||
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
|
||||
{:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do
|
||||
{:ok, activity}
|
||||
else
|
||||
|
|
@ -342,11 +408,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = _data
|
||||
%{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = data
|
||||
) do
|
||||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <-
|
||||
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||
with actor <- get_actor(data),
|
||||
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
|
||||
{:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false) do
|
||||
{:ok, activity}
|
||||
else
|
||||
|
|
@ -390,13 +456,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|
||||
# TODO: Make secure.
|
||||
def handle_incoming(
|
||||
%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = _data
|
||||
%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = data
|
||||
) do
|
||||
object_id = Utils.get_ap_id(object_id)
|
||||
|
||||
with %User{} = _actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <-
|
||||
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||
with actor <- get_actor(data),
|
||||
%User{} = _actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
|
||||
{:ok, activity} <- ActivityPub.delete(object, false) do
|
||||
{:ok, activity}
|
||||
else
|
||||
|
|
@ -410,11 +476,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
"object" => %{"type" => "Announce", "object" => object_id},
|
||||
"actor" => actor,
|
||||
"id" => id
|
||||
} = _data
|
||||
} = data
|
||||
) do
|
||||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <-
|
||||
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||
with actor <- get_actor(data),
|
||||
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
|
||||
{:ok, activity, _} <- ActivityPub.unannounce(actor, object, id, false) do
|
||||
{:ok, activity}
|
||||
else
|
||||
|
|
@ -440,9 +506,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
@ap_config Application.get_env(:pleroma, :activitypub)
|
||||
@accept_blocks Keyword.get(@ap_config, :accept_blocks)
|
||||
|
||||
def handle_incoming(
|
||||
%{
|
||||
"type" => "Undo",
|
||||
|
|
@ -451,7 +514,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
"id" => id
|
||||
} = _data
|
||||
) do
|
||||
with true <- @accept_blocks,
|
||||
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
|
||||
%User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
|
||||
%User{} = blocker <- User.get_or_fetch_by_ap_id(blocker),
|
||||
{:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do
|
||||
|
|
@ -465,7 +528,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
def handle_incoming(
|
||||
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = data
|
||||
) do
|
||||
with true <- @accept_blocks,
|
||||
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
|
||||
%User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
|
||||
%User{} = blocker = User.get_or_fetch_by_ap_id(blocker),
|
||||
{:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
|
||||
|
|
@ -483,11 +546,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
"object" => %{"type" => "Like", "object" => object_id},
|
||||
"actor" => actor,
|
||||
"id" => id
|
||||
} = _data
|
||||
} = data
|
||||
) do
|
||||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <-
|
||||
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||
with actor <- get_actor(data),
|
||||
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
|
||||
{:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do
|
||||
{:ok, activity}
|
||||
else
|
||||
|
|
@ -497,6 +560,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|
||||
def handle_incoming(_), do: :error
|
||||
|
||||
def fetch_obj_helper(id) when is_bitstring(id), do: ActivityPub.fetch_object_from_id(id)
|
||||
def fetch_obj_helper(obj) when is_map(obj), do: ActivityPub.fetch_object_from_id(obj["id"])
|
||||
|
||||
def get_obj_helper(id) do
|
||||
if object = Object.normalize(id), do: {:ok, object}, else: nil
|
||||
end
|
||||
|
|
@ -523,6 +589,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> prepare_attachments
|
||||
|> set_conversation
|
||||
|> set_reply_to_uri
|
||||
|> strip_internal_fields
|
||||
|> strip_internal_tags
|
||||
end
|
||||
|
||||
# @doc
|
||||
|
|
@ -538,7 +606,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
|
|
@ -557,7 +625,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
|
|
@ -575,7 +643,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
|
|
@ -585,14 +653,14 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
data =
|
||||
data
|
||||
|> maybe_fix_object_url
|
||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
|
||||
def maybe_fix_object_url(data) do
|
||||
if is_binary(data["object"]) and not String.starts_with?(data["object"], "http") do
|
||||
case ActivityPub.fetch_object_from_id(data["object"]) do
|
||||
case fetch_obj_helper(data["object"]) do
|
||||
{:ok, relative_object} ->
|
||||
if relative_object.data["external_url"] do
|
||||
_data =
|
||||
|
|
@ -627,12 +695,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
|
||||
def add_mention_tags(object) do
|
||||
recipients = object["to"] ++ (object["cc"] || [])
|
||||
|
||||
mentions =
|
||||
recipients
|
||||
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|
||||
|> Enum.filter(& &1)
|
||||
object
|
||||
|> Utils.get_notified_from_object()
|
||||
|> Enum.map(fn user ->
|
||||
%{"type" => "Mention", "href" => user.ap_id, "name" => "@#{user.nickname}"}
|
||||
end)
|
||||
|
|
@ -692,6 +757,29 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> Map.put("attachment", attachments)
|
||||
end
|
||||
|
||||
defp strip_internal_fields(object) do
|
||||
object
|
||||
|> Map.drop([
|
||||
"likes",
|
||||
"like_count",
|
||||
"announcements",
|
||||
"announcement_count",
|
||||
"emoji",
|
||||
"context_id"
|
||||
])
|
||||
end
|
||||
|
||||
defp strip_internal_tags(%{"tag" => tags} = object) do
|
||||
tags =
|
||||
tags
|
||||
|> Enum.filter(fn x -> is_map(x) end)
|
||||
|
||||
object
|
||||
|> Map.put("tag", tags)
|
||||
end
|
||||
|
||||
defp strip_internal_tags(object), do: object
|
||||
|
||||
defp user_upgrade_task(user) do
|
||||
old_follower_address = User.ap_followers(user)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
defmodule Pleroma.Web.ActivityPub.Utils do
|
||||
alias Pleroma.{Repo, Web, Object, Activity, User}
|
||||
alias Pleroma.{Repo, Web, Object, Activity, User, Notification}
|
||||
alias Pleroma.Web.Router.Helpers
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Ecto.{Changeset, UUID}
|
||||
import Ecto.Query
|
||||
require Logger
|
||||
|
||||
@supported_object_types ["Article", "Note", "Video", "Page"]
|
||||
|
||||
# Some implementations send the actor URI as the actor field, others send the entire actor object,
|
||||
# so figure out what the actor's URI is based on what we have.
|
||||
def get_ap_id(object) do
|
||||
|
|
@ -19,22 +21,58 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
Map.put(params, "actor", get_ap_id(params["actor"]))
|
||||
end
|
||||
|
||||
defp recipient_in_collection(ap_id, coll) when is_binary(coll), do: ap_id == coll
|
||||
defp recipient_in_collection(ap_id, coll) when is_list(coll), do: ap_id in coll
|
||||
defp recipient_in_collection(_, _), do: false
|
||||
|
||||
def recipient_in_message(ap_id, params) do
|
||||
cond do
|
||||
recipient_in_collection(ap_id, params["to"]) ->
|
||||
true
|
||||
|
||||
recipient_in_collection(ap_id, params["cc"]) ->
|
||||
true
|
||||
|
||||
recipient_in_collection(ap_id, params["bto"]) ->
|
||||
true
|
||||
|
||||
recipient_in_collection(ap_id, params["bcc"]) ->
|
||||
true
|
||||
|
||||
# if the message is unaddressed at all, then assume it is directly addressed
|
||||
# to the recipient
|
||||
!params["to"] && !params["cc"] && !params["bto"] && !params["bcc"] ->
|
||||
true
|
||||
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_list(target) when is_binary(target), do: [target]
|
||||
defp extract_list(lst) when is_list(lst), do: lst
|
||||
defp extract_list(_), do: []
|
||||
|
||||
def maybe_splice_recipient(ap_id, params) do
|
||||
need_splice =
|
||||
!recipient_in_collection(ap_id, params["to"]) &&
|
||||
!recipient_in_collection(ap_id, params["cc"])
|
||||
|
||||
cc_list = extract_list(params["cc"])
|
||||
|
||||
if need_splice do
|
||||
params
|
||||
|> Map.put("cc", [ap_id | cc_list])
|
||||
else
|
||||
params
|
||||
end
|
||||
end
|
||||
|
||||
def make_json_ld_header do
|
||||
%{
|
||||
"@context" => [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
%{
|
||||
"manuallyApprovesFollowers" => "as:manuallyApprovesFollowers",
|
||||
"sensitive" => "as:sensitive",
|
||||
"Hashtag" => "as:Hashtag",
|
||||
"ostatus" => "http://ostatus.org#",
|
||||
"atomUri" => "ostatus:atomUri",
|
||||
"inReplyToAtomUri" => "ostatus:inReplyToAtomUri",
|
||||
"conversation" => "ostatus:conversation",
|
||||
"toot" => "http://joinmastodon.org/ns#",
|
||||
"Emoji" => "toot:Emoji"
|
||||
}
|
||||
"#{Web.base_url()}/schemas/litepub-0.1.jsonld"
|
||||
]
|
||||
}
|
||||
end
|
||||
|
|
@ -59,6 +97,21 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
"#{Web.base_url()}/#{type}/#{UUID.generate()}"
|
||||
end
|
||||
|
||||
def get_notified_from_object(%{"type" => type} = object) when type in @supported_object_types do
|
||||
fake_create_activity = %{
|
||||
"to" => object["to"],
|
||||
"cc" => object["cc"],
|
||||
"type" => "Create",
|
||||
"object" => object
|
||||
}
|
||||
|
||||
Notification.get_notified_from_activity(%Activity{data: fake_create_activity}, false)
|
||||
end
|
||||
|
||||
def get_notified_from_object(object) do
|
||||
Notification.get_notified_from_activity(%Activity{data: object}, false)
|
||||
end
|
||||
|
||||
def create_context(context) do
|
||||
context = context || generate_id("contexts")
|
||||
changeset = Object.context_mapping(context)
|
||||
|
|
@ -128,7 +181,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
Inserts a full object if it is contained in an activity.
|
||||
"""
|
||||
def insert_full_object(%{"object" => %{"type" => type} = object_data})
|
||||
when is_map(object_data) and type in ["Article", "Note", "Video"] do
|
||||
when is_map(object_data) and type in @supported_object_types do
|
||||
with {:ok, _} <- Object.create(object_data) do
|
||||
:ok
|
||||
end
|
||||
|
|
@ -247,11 +300,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
"actor" => follower_id,
|
||||
"to" => [followed_id],
|
||||
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"object" => followed_id
|
||||
"object" => followed_id,
|
||||
"state" => "pending"
|
||||
}
|
||||
|
||||
data = if activity_id, do: Map.put(data, "id", activity_id), else: data
|
||||
data = if User.locked?(followed), do: Map.put(data, "state", "pending"), else: data
|
||||
|
||||
data
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,27 +1,23 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectView do
|
||||
use Pleroma.Web, :view
|
||||
alias Pleroma.{Object, Activity}
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
def render("object.json", %{object: object}) do
|
||||
base = %{
|
||||
"@context" => [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
%{
|
||||
"manuallyApprovesFollowers" => "as:manuallyApprovesFollowers",
|
||||
"sensitive" => "as:sensitive",
|
||||
"Hashtag" => "as:Hashtag",
|
||||
"ostatus" => "http://ostatus.org#",
|
||||
"atomUri" => "ostatus:atomUri",
|
||||
"inReplyToAtomUri" => "ostatus:inReplyToAtomUri",
|
||||
"conversation" => "ostatus:conversation",
|
||||
"toot" => "http://joinmastodon.org/ns#",
|
||||
"Emoji" => "toot:Emoji"
|
||||
}
|
||||
]
|
||||
}
|
||||
def render("object.json", %{object: %Object{} = object}) do
|
||||
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
|
||||
|
||||
additional = Transmogrifier.prepare_object(object.data)
|
||||
Map.merge(base, additional)
|
||||
end
|
||||
|
||||
def render("object.json", %{object: %Activity{} = activity}) do
|
||||
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
|
||||
object = Object.normalize(activity.data["object"])
|
||||
|
||||
additional =
|
||||
Transmogrifier.prepare_object(activity.data)
|
||||
|> Map.put("object", Transmogrifier.prepare_object(object.data))
|
||||
|
||||
Map.merge(base, additional)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
public_key = :public_key.pem_encode([public_key])
|
||||
|
||||
%{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => user.ap_id,
|
||||
"type" => "Application",
|
||||
"following" => "#{user.ap_id}/following",
|
||||
|
|
@ -36,6 +35,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
"sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox"
|
||||
}
|
||||
}
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render("user.json", %{user: user}) do
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
|
||||
def favorite(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.normalize(activity.data["object"]["id"]) do
|
||||
ActivityPub.like(user, object)
|
||||
else
|
||||
|
|
@ -47,7 +46,6 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
|
||||
def unfavorite(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.normalize(activity.data["object"]["id"]) do
|
||||
ActivityPub.unlike(user, object)
|
||||
else
|
||||
|
|
@ -72,22 +70,37 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
|
||||
def get_visibility(_), do: "public"
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@limit Keyword.get(@instance, :limit)
|
||||
defp get_content_type(content_type) do
|
||||
if Enum.member?(Pleroma.Config.get([:instance, :allowed_post_formats]), content_type) do
|
||||
content_type
|
||||
else
|
||||
"text/plain"
|
||||
end
|
||||
end
|
||||
|
||||
def post(user, %{"status" => status} = data) do
|
||||
visibility = get_visibility(data)
|
||||
limit = Pleroma.Config.get([:instance, :limit])
|
||||
|
||||
with status <- String.trim(status),
|
||||
length when length in 1..@limit <- String.length(status),
|
||||
attachments <- attachments_from_ids(data["media_ids"]),
|
||||
mentions <- Formatter.parse_mentions(status),
|
||||
inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]),
|
||||
{to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility),
|
||||
tags <- Formatter.parse_tags(status, data),
|
||||
content_html <-
|
||||
make_content_html(status, mentions, attachments, tags, data["no_attachment_links"]),
|
||||
make_content_html(
|
||||
status,
|
||||
mentions,
|
||||
attachments,
|
||||
tags,
|
||||
get_content_type(data["content_type"]),
|
||||
data["no_attachment_links"]
|
||||
),
|
||||
context <- make_context(inReplyTo),
|
||||
cw <- data["spoiler_text"],
|
||||
full_payload <- String.trim(status <> (data["spoiler_text"] || "")),
|
||||
length when length in 1..limit <- String.length(full_payload),
|
||||
object <-
|
||||
make_note_data(
|
||||
user.ap_id,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
alias Pleroma.{Repo, Object, Formatter, Activity}
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.MediaProxy
|
||||
alias Pleroma.User
|
||||
alias Calendar.Strftime
|
||||
alias Comeonin.Pbkdf2
|
||||
|
|
@ -18,6 +19,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
end
|
||||
end
|
||||
|
||||
def get_replied_to_activity(""), do: nil
|
||||
|
||||
def get_replied_to_activity(id) when not is_nil(id) do
|
||||
Repo.get(Activity, id)
|
||||
end
|
||||
|
|
@ -31,21 +34,29 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
end
|
||||
|
||||
def to_for_user_and_mentions(user, mentions, inReplyTo, "public") do
|
||||
to = ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
|
||||
mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end)
|
||||
cc = [user.follower_address | mentioned_users]
|
||||
|
||||
to = ["https://www.w3.org/ns/activitystreams#Public" | mentioned_users]
|
||||
cc = [user.follower_address]
|
||||
|
||||
if inReplyTo do
|
||||
{to, Enum.uniq([inReplyTo.data["actor"] | cc])}
|
||||
{Enum.uniq([inReplyTo.data["actor"] | to]), cc}
|
||||
else
|
||||
{to, cc}
|
||||
end
|
||||
end
|
||||
|
||||
def to_for_user_and_mentions(user, mentions, inReplyTo, "unlisted") do
|
||||
{to, cc} = to_for_user_and_mentions(user, mentions, inReplyTo, "public")
|
||||
{cc, to}
|
||||
mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end)
|
||||
|
||||
to = [user.follower_address | mentioned_users]
|
||||
cc = ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
|
||||
if inReplyTo do
|
||||
{Enum.uniq([inReplyTo.data["actor"] | to]), cc}
|
||||
else
|
||||
{to, cc}
|
||||
end
|
||||
end
|
||||
|
||||
def to_for_user_and_mentions(user, mentions, inReplyTo, "private") do
|
||||
|
|
@ -63,9 +74,16 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
end
|
||||
end
|
||||
|
||||
def make_content_html(status, mentions, attachments, tags, no_attachment_links \\ false) do
|
||||
def make_content_html(
|
||||
status,
|
||||
mentions,
|
||||
attachments,
|
||||
tags,
|
||||
content_type,
|
||||
no_attachment_links \\ false
|
||||
) do
|
||||
status
|
||||
|> format_input(mentions, tags)
|
||||
|> format_input(mentions, tags, content_type)
|
||||
|> maybe_add_attachments(attachments, no_attachment_links)
|
||||
end
|
||||
|
||||
|
|
@ -81,8 +99,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
def add_attachments(text, attachments) do
|
||||
attachment_text =
|
||||
Enum.map(attachments, fn
|
||||
%{"url" => [%{"href" => href} | _]} ->
|
||||
name = URI.decode(Path.basename(href))
|
||||
%{"url" => [%{"href" => href} | _]} = attachment ->
|
||||
name = attachment["name"] || URI.decode(Path.basename(href))
|
||||
href = MediaProxy.url(href)
|
||||
"<a href=\"#{href}\" class='attachment'>#{shortname(name)}</a>"
|
||||
|
||||
_ ->
|
||||
|
|
@ -92,9 +111,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
Enum.join([text | attachment_text], "<br>")
|
||||
end
|
||||
|
||||
def format_input(text, mentions, tags) do
|
||||
def format_input(text, mentions, tags, "text/plain") do
|
||||
text
|
||||
|> Formatter.html_escape()
|
||||
|> Formatter.html_escape("text/plain")
|
||||
|> String.replace(~r/\r?\n/, "<br>")
|
||||
|> (&{[], &1}).()
|
||||
|> Formatter.add_links()
|
||||
|
|
@ -103,6 +122,26 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
|> Formatter.finalize()
|
||||
end
|
||||
|
||||
def format_input(text, mentions, tags, "text/html") do
|
||||
text
|
||||
|> Formatter.html_escape("text/html")
|
||||
|> String.replace(~r/\r?\n/, "<br>")
|
||||
|> (&{[], &1}).()
|
||||
|> Formatter.add_user_links(mentions)
|
||||
|> Formatter.finalize()
|
||||
end
|
||||
|
||||
def format_input(text, mentions, tags, "text/markdown") do
|
||||
text
|
||||
|> Earmark.as_html!()
|
||||
|> Formatter.html_escape("text/html")
|
||||
|> String.replace(~r/\r?\n/, "")
|
||||
|> (&{[], &1}).()
|
||||
|> Formatter.add_user_links(mentions)
|
||||
|> Formatter.add_hashtag_links(tags)
|
||||
|> Formatter.finalize()
|
||||
end
|
||||
|
||||
def add_tag_links(text, tags) do
|
||||
tags =
|
||||
tags
|
||||
|
|
|
|||
|
|
@ -11,13 +11,16 @@ defmodule Pleroma.Web.Endpoint do
|
|||
#
|
||||
# You should set gzip to true if you are running phoenix.digest
|
||||
# when deploying your static files in production.
|
||||
plug(CORSPlug)
|
||||
|
||||
plug(Plug.Static, at: "/media", from: Pleroma.Uploaders.Local.upload_path(), gzip: false)
|
||||
|
||||
plug(
|
||||
Plug.Static,
|
||||
at: "/",
|
||||
from: "priv_sid/static",
|
||||
only: ~w(index.html static finmoji emoji packs sounds images instance sw.js favicon.png)
|
||||
only:
|
||||
~w(index.html static finmoji emoji packs sounds images instance sw.js favicon.png schemas)
|
||||
)
|
||||
|
||||
# Code reloading can be explicitly enabled under the
|
||||
|
|
|
|||
|
|
@ -7,13 +7,12 @@ defmodule Pleroma.Web.Federator do
|
|||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.OStatus
|
||||
require Logger
|
||||
|
||||
@websub Application.get_env(:pleroma, :websub)
|
||||
@ostatus Application.get_env(:pleroma, :ostatus)
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@federating Keyword.get(@instance, :federating)
|
||||
@max_jobs 20
|
||||
|
||||
def init(args) do
|
||||
|
|
@ -65,15 +64,17 @@ defmodule Pleroma.Web.Federator do
|
|||
{:ok, actor} = WebFinger.ensure_keys_present(actor)
|
||||
|
||||
if ActivityPub.is_public?(activity) do
|
||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end)
|
||||
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
|
||||
if OStatus.is_representable?(activity) do
|
||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end)
|
||||
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
|
||||
|
||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via Salmon" end)
|
||||
Pleroma.Web.Salmon.publish(actor, activity)
|
||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via Salmon" end)
|
||||
Pleroma.Web.Salmon.publish(actor, activity)
|
||||
end
|
||||
|
||||
if Mix.env() != :test do
|
||||
if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do
|
||||
Logger.info(fn -> "Relaying #{activity.data["id"]} out" end)
|
||||
Pleroma.Web.ActivityPub.Relay.publish(activity)
|
||||
Relay.publish(activity)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -147,7 +148,7 @@ defmodule Pleroma.Web.Federator do
|
|||
end
|
||||
|
||||
def enqueue(type, payload, priority \\ 1) do
|
||||
if @federating do
|
||||
if Pleroma.Config.get([:instance, :federating]) do
|
||||
if Mix.env() == :test do
|
||||
handle(type, payload)
|
||||
else
|
||||
|
|
|
|||
|
|
@ -35,6 +35,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
||||
original_user = user
|
||||
|
||||
avatar_upload_limit =
|
||||
Application.get_env(:pleroma, :instance)
|
||||
|> Keyword.fetch(:avatar_upload_limit)
|
||||
|
||||
banner_upload_limit =
|
||||
Application.get_env(:pleroma, :instance)
|
||||
|> Keyword.fetch(:banner_upload_limit)
|
||||
|
||||
params =
|
||||
if bio = params["note"] do
|
||||
Map.put(params, "bio", bio)
|
||||
|
|
@ -52,7 +60,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
user =
|
||||
if avatar = params["avatar"] do
|
||||
with %Plug.Upload{} <- avatar,
|
||||
{:ok, object} <- ActivityPub.upload(avatar),
|
||||
{:ok, object} <- ActivityPub.upload(avatar, avatar_upload_limit),
|
||||
change = Ecto.Changeset.change(user, %{avatar: object.data}),
|
||||
{:ok, user} = User.update_and_set_cache(change) do
|
||||
user
|
||||
|
|
@ -66,7 +74,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
user =
|
||||
if banner = params["header"] do
|
||||
with %Plug.Upload{} <- banner,
|
||||
{:ok, object} <- ActivityPub.upload(banner),
|
||||
{:ok, object} <- ActivityPub.upload(banner, banner_upload_limit),
|
||||
new_info <- Map.put(user.info, "banner", object.data),
|
||||
change <- User.info_changeset(user, %{info: new_info}),
|
||||
{:ok, user} <- User.update_and_set_cache(change) do
|
||||
|
|
@ -124,22 +132,23 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@mastodon_api_level "2.5.0"
|
||||
|
||||
def masto_instance(conn, _params) do
|
||||
instance = Pleroma.Config.get(:instance)
|
||||
|
||||
response = %{
|
||||
uri: Web.base_url(),
|
||||
title: Keyword.get(@instance, :name),
|
||||
description: Keyword.get(@instance, :description),
|
||||
version: "#{@mastodon_api_level} (compatible; #{Keyword.get(@instance, :version)})",
|
||||
email: Keyword.get(@instance, :email),
|
||||
title: Keyword.get(instance, :name),
|
||||
description: Keyword.get(instance, :description),
|
||||
version: "#{@mastodon_api_level} (compatible; #{Keyword.get(instance, :version)})",
|
||||
email: Keyword.get(instance, :email),
|
||||
urls: %{
|
||||
streaming_api: String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws")
|
||||
},
|
||||
stats: Stats.get_stats(),
|
||||
thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg",
|
||||
max_toot_chars: Keyword.get(@instance, :limit)
|
||||
max_toot_chars: Keyword.get(instance, :limit)
|
||||
}
|
||||
|
||||
json(conn, response)
|
||||
|
|
@ -150,7 +159,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
defp mastodonized_emoji do
|
||||
Pleroma.Formatter.get_custom_emoji()
|
||||
Pleroma.Emoji.get_all()
|
||||
|> Enum.map(fn {shortcode, relative_url} ->
|
||||
url = to_string(URI.merge(Web.base_url(), relative_url))
|
||||
|
||||
|
|
@ -223,6 +232,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|
||||
activities =
|
||||
ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||
|> ActivityPub.contain_timeline(user)
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
|
|
@ -282,7 +292,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||
true <- ActivityPub.visible_for_user?(activity, user) do
|
||||
render(conn, StatusView, "status.json", %{activity: activity, for: user})
|
||||
try_render(conn, StatusView, "status.json", %{activity: activity, for: user})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -345,7 +355,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
{:ok, activity} =
|
||||
Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> CommonAPI.post(user, params) end)
|
||||
|
||||
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
end
|
||||
|
||||
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
|
|
@ -361,28 +371,28 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|
||||
def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||
with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user) do
|
||||
render(conn, StatusView, "status.json", %{activity: announce, for: user, as: :activity})
|
||||
try_render(conn, StatusView, "status.json", %{activity: announce, for: user, as: :activity})
|
||||
end
|
||||
end
|
||||
|
||||
def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||
with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(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})
|
||||
try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
end
|
||||
end
|
||||
|
||||
def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||
with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(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})
|
||||
try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
end
|
||||
end
|
||||
|
||||
def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||
with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user),
|
||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -434,6 +444,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
render(conn, AccountView, "relationships.json", %{user: user, targets: targets})
|
||||
end
|
||||
|
||||
# Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array.
|
||||
def relationships(%{assigns: %{user: user}} = conn, _) do
|
||||
conn
|
||||
|> json([])
|
||||
end
|
||||
|
||||
def update_media(%{assigns: %{user: _}} = conn, data) do
|
||||
with %Object{} = object <- Repo.get(Object, data["id"]),
|
||||
true <- is_binary(data["description"]),
|
||||
|
|
@ -499,6 +515,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|> Map.put("type", "Create")
|
||||
|> Map.put("local_only", local_only)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("tag", String.downcase(params["tag"]))
|
||||
|
||||
activities =
|
||||
ActivityPub.fetch_public_activities(params)
|
||||
|
|
@ -574,7 +591,13 @@ 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.maybe_direct_follow(follower, followed),
|
||||
{:ok, _activity} <- ActivityPub.follow(follower, followed) do
|
||||
{:ok, _activity} <- ActivityPub.follow(follower, followed),
|
||||
{:ok, follower, followed} <-
|
||||
User.wait_and_refresh(
|
||||
Pleroma.Config.get([:activitypub, :follow_handshake_timeout]),
|
||||
follower,
|
||||
followed
|
||||
) do
|
||||
render(conn, AccountView, "relationship.json", %{user: follower, target: followed})
|
||||
else
|
||||
{:error, message} ->
|
||||
|
|
@ -765,6 +788,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def account_lists(%{assigns: %{user: user}} = conn, %{"id" => account_id}) do
|
||||
lists = Pleroma.List.get_lists_account_belongs(user, account_id)
|
||||
res = ListView.render("lists.json", lists: lists)
|
||||
json(conn, res)
|
||||
end
|
||||
|
||||
def delete_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
||||
{:ok, _list} <- Pleroma.List.delete(list) do
|
||||
|
|
@ -859,6 +888,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
if user && token do
|
||||
mastodon_emoji = mastodonized_emoji()
|
||||
|
||||
limit = Pleroma.Config.get([:instance, :limit])
|
||||
|
||||
accounts =
|
||||
Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user}))
|
||||
|
||||
|
|
@ -878,7 +909,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
auto_play_gif: false,
|
||||
display_sensitive_media: false,
|
||||
reduce_motion: false,
|
||||
max_toot_chars: Keyword.get(@instance, :limit)
|
||||
max_toot_chars: limit
|
||||
},
|
||||
rights: %{
|
||||
delete_others_notice: !!user.info["is_moderator"]
|
||||
|
|
@ -938,7 +969,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
push_subscription: nil,
|
||||
accounts: accounts,
|
||||
custom_emojis: mastodon_emoji,
|
||||
char_limit: Keyword.get(@instance, :limit)
|
||||
char_limit: limit
|
||||
}
|
||||
|> Jason.encode!()
|
||||
|
||||
|
|
@ -964,9 +995,29 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def login(conn, %{"code" => code}) do
|
||||
with {:ok, app} <- get_or_make_app(),
|
||||
%Authorization{} = auth <- Repo.get_by(Authorization, token: code, app_id: app.id),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
conn
|
||||
|> put_session(:oauth_token, token.token)
|
||||
|> redirect(to: "/web/getting-started")
|
||||
end
|
||||
end
|
||||
|
||||
def login(conn, _) do
|
||||
conn
|
||||
|> render(MastodonView, "login.html", %{error: false})
|
||||
with {:ok, app} <- get_or_make_app() do
|
||||
path =
|
||||
o_auth_path(conn, :authorize,
|
||||
response_type: "code",
|
||||
client_id: app.client_id,
|
||||
redirect_uri: ".",
|
||||
scope: app.scopes
|
||||
)
|
||||
|
||||
conn
|
||||
|> redirect(to: path)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_or_make_app() do
|
||||
|
|
@ -985,22 +1036,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def login_post(conn, %{"authorization" => %{"name" => name, "password" => password}}) do
|
||||
with %User{} = user <- User.get_by_nickname_or_email(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
|
||||
|
|
@ -1144,18 +1179,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|> json("Something went wrong")
|
||||
end
|
||||
|
||||
@suggestions Application.get_env(:pleroma, :suggestions)
|
||||
|
||||
def suggestions(%{assigns: %{user: user}} = conn, _) do
|
||||
if Keyword.get(@suggestions, :enabled, false) do
|
||||
api = Keyword.get(@suggestions, :third_party_engine, "")
|
||||
timeout = Keyword.get(@suggestions, :timeout, 5000)
|
||||
limit = Keyword.get(@suggestions, :limit, 23)
|
||||
suggestions = Pleroma.Config.get(:suggestions)
|
||||
|
||||
host =
|
||||
Application.get_env(:pleroma, Pleroma.Web.Endpoint)
|
||||
|> Keyword.get(:url)
|
||||
|> Keyword.get(:host)
|
||||
if Keyword.get(suggestions, :enabled, false) do
|
||||
api = Keyword.get(suggestions, :third_party_engine, "")
|
||||
timeout = Keyword.get(suggestions, :timeout, 5000)
|
||||
limit = Keyword.get(suggestions, :limit, 23)
|
||||
|
||||
host = Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host])
|
||||
|
||||
user = user.nickname
|
||||
url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user)
|
||||
|
|
@ -1191,4 +1223,23 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
json(conn, [])
|
||||
end
|
||||
end
|
||||
|
||||
def try_render(conn, renderer, target, params)
|
||||
when is_binary(target) do
|
||||
res = render(conn, renderer, target, params)
|
||||
|
||||
if res == nil do
|
||||
conn
|
||||
|> put_status(501)
|
||||
|> json(%{error: "Can't display this activity"})
|
||||
else
|
||||
res
|
||||
end
|
||||
end
|
||||
|
||||
def try_render(conn, _, _, _) do
|
||||
conn
|
||||
|> put_status(501)
|
||||
|> json(%{error: "Can't display this activity"})
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,15 +26,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonSocket do
|
|||
"list",
|
||||
"hashtag"
|
||||
] <- params["stream"] do
|
||||
topic = if stream == "list", do: "list:#{params["list"]}", else: stream
|
||||
socket_stream = if stream == "hashtag", do: "hashtag:#{params["tag"]}", else: stream
|
||||
topic =
|
||||
case stream do
|
||||
"hashtag" -> "hashtag:#{params["tag"]}"
|
||||
"list" -> "list:#{params["list"]}"
|
||||
_ -> stream
|
||||
end
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:topic, topic)
|
||||
|> assign(:user, user)
|
||||
|
||||
Pleroma.Web.Streamer.add_socket(socket_stream, socket)
|
||||
Pleroma.Web.Streamer.add_socket(topic, socket)
|
||||
{:ok, socket}
|
||||
else
|
||||
_e -> :error
|
||||
|
|
|
|||
|
|
@ -72,6 +72,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
end
|
||||
|
||||
def render("relationship.json", %{user: user, target: target}) do
|
||||
follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target)
|
||||
|
||||
requested =
|
||||
if follow_activity do
|
||||
follow_activity.data["state"] == "pending"
|
||||
else
|
||||
false
|
||||
end
|
||||
|
||||
%{
|
||||
id: to_string(target.id),
|
||||
following: User.following?(user, target),
|
||||
|
|
@ -79,7 +88,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
blocking: User.blocks?(user, target),
|
||||
muting: false,
|
||||
muting_notifications: false,
|
||||
requested: false,
|
||||
requested: requested,
|
||||
domain_blocking: false,
|
||||
showing_reblogs: false,
|
||||
endorsed: false
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
"status.json",
|
||||
Map.put(opts, :replied_to_activities, replied_to_activities)
|
||||
)
|
||||
|> Enum.filter(fn x -> not is_nil(x) end)
|
||||
end
|
||||
|
||||
def render(
|
||||
|
|
@ -60,7 +61,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
in_reply_to_id: nil,
|
||||
in_reply_to_account_id: nil,
|
||||
reblog: reblogged,
|
||||
content: reblogged[:content],
|
||||
content: reblogged[:content] || "",
|
||||
created_at: created_at,
|
||||
reblogs_count: 0,
|
||||
replies_count: 0,
|
||||
|
|
@ -158,10 +159,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
}
|
||||
end
|
||||
|
||||
def render("status.json", _) do
|
||||
nil
|
||||
end
|
||||
|
||||
def render("attachment.json", %{attachment: attachment}) do
|
||||
[attachment_url | _] = attachment["url"]
|
||||
media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image"
|
||||
href = attachment_url["href"]
|
||||
href = attachment_url["href"] |> MediaProxy.url()
|
||||
|
||||
type =
|
||||
cond do
|
||||
|
|
@ -175,9 +180,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
|
||||
%{
|
||||
id: to_string(attachment["id"] || hash_id),
|
||||
url: MediaProxy.url(href),
|
||||
url: href,
|
||||
remote_url: href,
|
||||
preview_url: MediaProxy.url(href),
|
||||
preview_url: href,
|
||||
text_url: href,
|
||||
type: type,
|
||||
description: attachment["name"]
|
||||
|
|
@ -225,24 +230,24 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
if !!name and name != "" do
|
||||
"<p><a href=\"#{object["id"]}\">#{name}</a></p>#{object["content"]}"
|
||||
else
|
||||
object["content"]
|
||||
object["content"] || ""
|
||||
end
|
||||
|
||||
content
|
||||
end
|
||||
|
||||
def render_content(%{"type" => "Article"} = object) do
|
||||
def render_content(%{"type" => object_type} = object) when object_type in ["Article", "Page"] do
|
||||
summary = object["name"]
|
||||
|
||||
content =
|
||||
if !!summary and summary != "" do
|
||||
if !!summary and summary != "" and is_bitstring(object["url"]) do
|
||||
"<p><a href=\"#{object["url"]}\">#{summary}</a></p>#{object["content"]}"
|
||||
else
|
||||
object["content"]
|
||||
object["content"] || ""
|
||||
end
|
||||
|
||||
content
|
||||
end
|
||||
|
||||
def render_content(object), do: object["content"]
|
||||
def render_content(object), do: object["content"] || ""
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
alias Pleroma.Stats
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.{User, Repo}
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
plug(Pleroma.Web.FederatingPlug)
|
||||
|
||||
def schemas(conn, _params) do
|
||||
response = %{
|
||||
|
|
@ -27,11 +30,59 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
gopher = Application.get_env(:pleroma, :gopher)
|
||||
stats = Stats.get_stats()
|
||||
|
||||
mrf_simple =
|
||||
Application.get_env(:pleroma, :mrf_simple)
|
||||
|> Enum.into(%{})
|
||||
|
||||
mrf_policies =
|
||||
MRF.get_policies()
|
||||
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
|
||||
|
||||
quarantined = Keyword.get(instance, :quarantined_instances)
|
||||
|
||||
quarantined =
|
||||
if is_list(quarantined) do
|
||||
quarantined
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
staff_accounts =
|
||||
User.moderator_user_query()
|
||||
|> Repo.all()
|
||||
|> Enum.map(fn u -> u.ap_id end)
|
||||
|
||||
mrf_transparency = Keyword.get(instance, :mrf_transparency)
|
||||
|
||||
federation_response =
|
||||
if mrf_transparency do
|
||||
%{
|
||||
mrf_policies: mrf_policies,
|
||||
mrf_simple: mrf_simple,
|
||||
quarantined_instances: quarantined
|
||||
}
|
||||
else
|
||||
%{}
|
||||
end
|
||||
|
||||
features = [
|
||||
"pleroma_api",
|
||||
"mastodon_api",
|
||||
"mastodon_api_streaming",
|
||||
if Keyword.get(media_proxy, :enabled) do
|
||||
"media_proxy"
|
||||
end,
|
||||
if Keyword.get(gopher, :enabled) do
|
||||
"gopher"
|
||||
end,
|
||||
if Keyword.get(chat, :enabled) do
|
||||
"chat"
|
||||
end,
|
||||
if Keyword.get(suggestions, :enabled) do
|
||||
"suggestions"
|
||||
end
|
||||
]
|
||||
|
||||
response = %{
|
||||
version: "2.0",
|
||||
software: %{
|
||||
|
|
@ -53,7 +104,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
metadata: %{
|
||||
nodeName: Keyword.get(instance, :name),
|
||||
nodeDescription: Keyword.get(instance, :description),
|
||||
mediaProxy: Keyword.get(media_proxy, :enabled),
|
||||
private: !Keyword.get(instance, :public, true),
|
||||
suggestions: %{
|
||||
enabled: Keyword.get(suggestions, :enabled, false),
|
||||
|
|
@ -63,8 +113,15 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
web: Keyword.get(suggestions, :web, "")
|
||||
},
|
||||
staffAccounts: staff_accounts,
|
||||
chat: Keyword.get(chat, :enabled),
|
||||
gopher: Keyword.get(gopher, :enabled)
|
||||
federation: federation_response,
|
||||
postFormats: Keyword.get(instance, :allowed_post_formats),
|
||||
uploadLimits: %{
|
||||
general: Keyword.get(instance, :upload_limit),
|
||||
avatar: Keyword.get(instance, :avatar_upload_limit),
|
||||
banner: Keyword.get(instance, :banner_upload_limit),
|
||||
background: Keyword.get(instance, :background_upload_limit)
|
||||
},
|
||||
features: features
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
|||
alias Pleroma.{User, Repo}
|
||||
alias Pleroma.Web.OAuth.{Authorization, App}
|
||||
|
||||
import Ecto.{Changeset}
|
||||
import Ecto.{Changeset, Query}
|
||||
|
||||
schema "oauth_authorizations" do
|
||||
field(:token, :string)
|
||||
|
|
@ -45,4 +45,12 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
|||
end
|
||||
|
||||
def use_token(%Authorization{used: true}), do: {:error, "already used"}
|
||||
|
||||
def delete_user_authorizations(%User{id: user_id}) do
|
||||
from(
|
||||
a in Pleroma.Web.OAuth.Authorization,
|
||||
where: a.user_id == ^user_id
|
||||
)
|
||||
|> Repo.delete_all()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -33,25 +33,35 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
||||
{:ok, auth} <- Authorization.create_authorization(app, user) do
|
||||
if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do
|
||||
render(conn, "results.html", %{
|
||||
auth: auth
|
||||
})
|
||||
else
|
||||
connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
|
||||
url = "#{redirect_uri}#{connector}"
|
||||
url_params = %{:code => auth.token}
|
||||
# Special case: Local MastodonFE.
|
||||
redirect_uri =
|
||||
if redirect_uri == "." do
|
||||
mastodon_api_url(conn, :login)
|
||||
else
|
||||
redirect_uri
|
||||
end
|
||||
|
||||
url_params =
|
||||
if params["state"] do
|
||||
Map.put(url_params, :state, params["state"])
|
||||
else
|
||||
url_params
|
||||
end
|
||||
cond do
|
||||
redirect_uri == "urn:ietf:wg:oauth:2.0:oob" ->
|
||||
render(conn, "results.html", %{
|
||||
auth: auth
|
||||
})
|
||||
|
||||
url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
|
||||
true ->
|
||||
connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
|
||||
url = "#{redirect_uri}#{connector}"
|
||||
url_params = %{:code => auth.token}
|
||||
|
||||
redirect(conn, external: url)
|
||||
url_params =
|
||||
if params["state"] do
|
||||
Map.put(url_params, :state, params["state"])
|
||||
else
|
||||
url_params
|
||||
end
|
||||
|
||||
url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
|
||||
|
||||
redirect(conn, external: url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -133,8 +143,11 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
end
|
||||
end
|
||||
|
||||
# XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
|
||||
# decoding it. Investigate sometime.
|
||||
defp fix_padding(token) do
|
||||
token
|
||||
|> URI.decode()
|
||||
|> Base.url_decode64!(padding: false)
|
||||
|> Base.url_encode64()
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
defmodule Pleroma.Web.OAuth.Token do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
alias Pleroma.{User, Repo}
|
||||
alias Pleroma.Web.OAuth.{Token, App, Authorization}
|
||||
|
||||
|
|
@ -35,4 +37,12 @@ defmodule Pleroma.Web.OAuth.Token do
|
|||
|
||||
Repo.insert(token)
|
||||
end
|
||||
|
||||
def delete_user_tokens(%User{id: user_id}) do
|
||||
from(
|
||||
t in Pleroma.Web.OAuth.Token,
|
||||
where: t.user_id == ^user_id
|
||||
)
|
||||
|> Repo.delete_all()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,6 +11,21 @@ defmodule Pleroma.Web.OStatus do
|
|||
alias Pleroma.Web.OStatus.{FollowHandler, UnfollowHandler, NoteHandler, DeleteHandler}
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
def is_representable?(%Activity{data: data}) do
|
||||
object = Object.normalize(data["object"])
|
||||
|
||||
cond do
|
||||
is_nil(object) ->
|
||||
false
|
||||
|
||||
object.data["type"] == "Note" ->
|
||||
true
|
||||
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def feed_path(user) do
|
||||
"#{user.ap_id}/feed.atom"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
|||
alias Pleroma.Web.ActivityPub.ActivityPubController
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
|
||||
plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
|
||||
action_fallback(:errors)
|
||||
|
||||
def feed_redirect(conn, %{"nickname" => nickname}) do
|
||||
|
|
|
|||
|
|
@ -3,12 +3,6 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
alias Pleroma.{Repo, User, Web.Router}
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@federating Keyword.get(@instance, :federating)
|
||||
@allow_relay Keyword.get(@instance, :allow_relay)
|
||||
@public Keyword.get(@instance, :public)
|
||||
@registrations_open Keyword.get(@instance, :registrations_open)
|
||||
|
||||
pipeline :api do
|
||||
plug(:accepts, ["json"])
|
||||
plug(:fetch_session)
|
||||
|
|
@ -119,6 +113,7 @@ defmodule Pleroma.Web.Router do
|
|||
post("/accounts/:id/unblock", MastodonAPIController, :unblock)
|
||||
post("/accounts/:id/mute", MastodonAPIController, :relationship_noop)
|
||||
post("/accounts/:id/unmute", MastodonAPIController, :relationship_noop)
|
||||
get("/accounts/:id/lists", MastodonAPIController, :account_lists)
|
||||
|
||||
get("/follow_requests", MastodonAPIController, :follow_requests)
|
||||
post("/follow_requests/:id/authorize", MastodonAPIController, :authorize_follow_request)
|
||||
|
|
@ -242,11 +237,7 @@ defmodule Pleroma.Web.Router do
|
|||
end
|
||||
|
||||
scope "/api", Pleroma.Web do
|
||||
if @public do
|
||||
pipe_through(:api)
|
||||
else
|
||||
pipe_through(:authenticated_api)
|
||||
end
|
||||
pipe_through(:api)
|
||||
|
||||
get("/statuses/public_timeline", TwitterAPI.Controller, :public_timeline)
|
||||
|
||||
|
|
@ -281,6 +272,10 @@ defmodule Pleroma.Web.Router do
|
|||
get("/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline)
|
||||
get("/qvitter/statuses/notifications", TwitterAPI.Controller, :notifications)
|
||||
|
||||
# XXX: this is really a pleroma API, but we want to keep the pleroma namespace clean
|
||||
# for now.
|
||||
post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read)
|
||||
|
||||
post("/statuses/update", TwitterAPI.Controller, :status_update)
|
||||
post("/statuses/retweet/:id", TwitterAPI.Controller, :retweet)
|
||||
post("/statuses/unretweet/:id", TwitterAPI.Controller, :unretweet)
|
||||
|
|
@ -330,12 +325,10 @@ defmodule Pleroma.Web.Router do
|
|||
get("/users/:nickname/feed", OStatus.OStatusController, :feed)
|
||||
get("/users/:nickname", OStatus.OStatusController, :feed_redirect)
|
||||
|
||||
if @federating do
|
||||
post("/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming)
|
||||
post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
|
||||
get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation)
|
||||
post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
|
||||
end
|
||||
post("/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming)
|
||||
post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
|
||||
get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation)
|
||||
post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
|
||||
end
|
||||
|
||||
pipeline :activitypub do
|
||||
|
|
@ -352,31 +345,27 @@ defmodule Pleroma.Web.Router do
|
|||
get("/users/:nickname/outbox", ActivityPubController, :outbox)
|
||||
end
|
||||
|
||||
if @federating do
|
||||
if @allow_relay do
|
||||
scope "/relay", Pleroma.Web.ActivityPub do
|
||||
pipe_through(:ap_relay)
|
||||
get("/", ActivityPubController, :relay)
|
||||
end
|
||||
end
|
||||
scope "/relay", Pleroma.Web.ActivityPub do
|
||||
pipe_through(:ap_relay)
|
||||
get("/", ActivityPubController, :relay)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.ActivityPub do
|
||||
pipe_through(:activitypub)
|
||||
post("/users/:nickname/inbox", ActivityPubController, :inbox)
|
||||
post("/inbox", ActivityPubController, :inbox)
|
||||
end
|
||||
scope "/", Pleroma.Web.ActivityPub do
|
||||
pipe_through(:activitypub)
|
||||
post("/users/:nickname/inbox", ActivityPubController, :inbox)
|
||||
post("/inbox", ActivityPubController, :inbox)
|
||||
end
|
||||
|
||||
scope "/.well-known", Pleroma.Web do
|
||||
pipe_through(:well_known)
|
||||
scope "/.well-known", Pleroma.Web do
|
||||
pipe_through(:well_known)
|
||||
|
||||
get("/host-meta", WebFinger.WebFingerController, :host_meta)
|
||||
get("/webfinger", WebFinger.WebFingerController, :webfinger)
|
||||
get("/nodeinfo", Nodeinfo.NodeinfoController, :schemas)
|
||||
end
|
||||
get("/host-meta", WebFinger.WebFingerController, :host_meta)
|
||||
get("/webfinger", WebFinger.WebFingerController, :webfinger)
|
||||
get("/nodeinfo", Nodeinfo.NodeinfoController, :schemas)
|
||||
end
|
||||
|
||||
scope "/nodeinfo", Pleroma.Web do
|
||||
get("/:version", Nodeinfo.NodeinfoController, :nodeinfo)
|
||||
end
|
||||
scope "/nodeinfo", Pleroma.Web do
|
||||
get("/:version", Nodeinfo.NodeinfoController, :nodeinfo)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.MastodonAPI do
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
<h2>Login 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 or email" %>
|
||||
<br>
|
||||
<%= password_input f, :password, placeholder: "Password" %>
|
||||
<br>
|
||||
<%= submit "Log in" %>
|
||||
<% end %>
|
||||
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
alias Pleroma.Web.WebFinger
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.{Formatter, Emoji}
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.{Repo, PasswordResetToken, User}
|
||||
|
||||
|
|
@ -134,19 +134,20 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
end
|
||||
end
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@instance_fe Application.get_env(:pleroma, :fe)
|
||||
@instance_chat Application.get_env(:pleroma, :chat)
|
||||
def config(conn, _params) do
|
||||
instance = Pleroma.Config.get(:instance)
|
||||
instance_fe = Pleroma.Config.get(:fe)
|
||||
instance_chat = Pleroma.Config.get(:chat)
|
||||
|
||||
case get_format(conn) do
|
||||
"xml" ->
|
||||
response = """
|
||||
<config>
|
||||
<site>
|
||||
<name>#{Keyword.get(@instance, :name)}</name>
|
||||
<name>#{Keyword.get(instance, :name)}</name>
|
||||
<site>#{Web.base_url()}</site>
|
||||
<textlimit>#{Keyword.get(@instance, :limit)}</textlimit>
|
||||
<closed>#{!Keyword.get(@instance, :registrations_open)}</closed>
|
||||
<textlimit>#{Keyword.get(instance, :limit)}</textlimit>
|
||||
<closed>#{!Keyword.get(instance, :registrations_open)}</closed>
|
||||
</site>
|
||||
</config>
|
||||
"""
|
||||
|
|
@ -157,29 +158,32 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
|
||||
_ ->
|
||||
data = %{
|
||||
name: Keyword.get(@instance, :name),
|
||||
description: Keyword.get(@instance, :description),
|
||||
name: Keyword.get(instance, :name),
|
||||
description: Keyword.get(instance, :description),
|
||||
server: Web.base_url(),
|
||||
textlimit: to_string(Keyword.get(@instance, :limit)),
|
||||
closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1"),
|
||||
private: if(Keyword.get(@instance, :public, true), do: "0", else: "1")
|
||||
textlimit: to_string(Keyword.get(instance, :limit)),
|
||||
closed: if(Keyword.get(instance, :registrations_open), do: "0", else: "1"),
|
||||
private: if(Keyword.get(instance, :public, true), do: "0", else: "1")
|
||||
}
|
||||
|
||||
pleroma_fe = %{
|
||||
theme: Keyword.get(@instance_fe, :theme),
|
||||
background: Keyword.get(@instance_fe, :background),
|
||||
logo: Keyword.get(@instance_fe, :logo),
|
||||
logoMask: Keyword.get(@instance_fe, :logo_mask),
|
||||
logoMargin: Keyword.get(@instance_fe, :logo_margin),
|
||||
redirectRootNoLogin: Keyword.get(@instance_fe, :redirect_root_no_login),
|
||||
redirectRootLogin: Keyword.get(@instance_fe, :redirect_root_login),
|
||||
chatDisabled: !Keyword.get(@instance_chat, :enabled),
|
||||
showInstanceSpecificPanel: Keyword.get(@instance_fe, :show_instance_panel),
|
||||
scopeOptionsEnabled: Keyword.get(@instance_fe, :scope_options_enabled),
|
||||
collapseMessageWithSubject: Keyword.get(@instance_fe, :collapse_message_with_subject)
|
||||
theme: Keyword.get(instance_fe, :theme),
|
||||
background: Keyword.get(instance_fe, :background),
|
||||
logo: Keyword.get(instance_fe, :logo),
|
||||
logoMask: Keyword.get(instance_fe, :logo_mask),
|
||||
logoMargin: Keyword.get(instance_fe, :logo_margin),
|
||||
redirectRootNoLogin: Keyword.get(instance_fe, :redirect_root_no_login),
|
||||
redirectRootLogin: Keyword.get(instance_fe, :redirect_root_login),
|
||||
chatDisabled: !Keyword.get(instance_chat, :enabled),
|
||||
showInstanceSpecificPanel: Keyword.get(instance_fe, :show_instance_panel),
|
||||
scopeOptionsEnabled: Keyword.get(instance_fe, :scope_options_enabled),
|
||||
formattingOptionsEnabled: Keyword.get(instance_fe, :formatting_options_enabled),
|
||||
collapseMessageWithSubject: Keyword.get(instance_fe, :collapse_message_with_subject),
|
||||
hidePostStats: Keyword.get(instance_fe, :hide_post_stats),
|
||||
hideUserStats: Keyword.get(instance_fe, :hide_user_stats)
|
||||
}
|
||||
|
||||
managed_config = Keyword.get(@instance, :managed_config)
|
||||
managed_config = Keyword.get(instance, :managed_config)
|
||||
|
||||
data =
|
||||
if managed_config do
|
||||
|
|
@ -193,7 +197,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
end
|
||||
|
||||
def version(conn, _params) do
|
||||
version = Keyword.get(@instance, :version)
|
||||
version = Pleroma.Config.get([:instance, :version])
|
||||
|
||||
case get_format(conn) do
|
||||
"xml" ->
|
||||
|
|
@ -209,7 +213,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
end
|
||||
|
||||
def emoji(conn, _params) do
|
||||
json(conn, Enum.into(Formatter.get_custom_emoji(), %{}))
|
||||
json(conn, Enum.into(Emoji.get_all(), %{}))
|
||||
end
|
||||
|
||||
def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
|
||||
|
|
@ -222,7 +226,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
|> Enum.map(fn account ->
|
||||
with %User{} = follower <- User.get_cached_by_ap_id(user.ap_id),
|
||||
%User{} = followed <- User.get_or_fetch(account),
|
||||
{:ok, follower} <- User.follow(follower, followed) do
|
||||
{:ok, follower} <- User.maybe_direct_follow(follower, followed) do
|
||||
ActivityPub.follow(follower, followed)
|
||||
else
|
||||
err -> Logger.debug("follow_import: following #{account} failed with #{inspect(err)}")
|
||||
|
|
|
|||
|
|
@ -180,6 +180,10 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
|
|||
|
||||
attachments = (object["attachment"] || []) ++ video
|
||||
|
||||
reply_parent = Activity.get_in_reply_to_activity(activity)
|
||||
|
||||
reply_user = reply_parent && User.get_cached_by_ap_id(reply_parent.actor)
|
||||
|
||||
%{
|
||||
"id" => activity.id,
|
||||
"uri" => activity.data["object"]["id"],
|
||||
|
|
@ -190,6 +194,10 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
|
|||
"is_post_verb" => true,
|
||||
"created_at" => created_at,
|
||||
"in_reply_to_status_id" => object["inReplyToStatusId"],
|
||||
"in_reply_to_screen_name" => reply_user && reply_user.nickname,
|
||||
"in_reply_to_profileurl" => User.profile_url(reply_user),
|
||||
"in_reply_to_ostatus_uri" => reply_user && reply_user.ap_id,
|
||||
"in_reply_to_user_id" => reply_user && reply_user.id,
|
||||
"statusnet_conversation_id" => conversation_id,
|
||||
"attachments" => attachments |> ObjectRepresenter.enum_to_list(opts),
|
||||
"attentions" => attentions,
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.TwitterAPI.UserView
|
||||
alias Pleroma.Web.{OStatus, CommonAPI}
|
||||
alias Pleroma.Web.MediaProxy
|
||||
import Ecto.Query
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
@registrations_open Keyword.get(@instance, :registrations_open)
|
||||
|
||||
def create_status(%User{} = user, %{"status" => _} = data) do
|
||||
CommonAPI.post(user, data)
|
||||
|
|
@ -23,7 +22,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
def follow(%User{} = follower, params) do
|
||||
with {:ok, %User{} = followed} <- get_user(params),
|
||||
{:ok, follower} <- User.maybe_direct_follow(follower, followed),
|
||||
{:ok, activity} <- ActivityPub.follow(follower, followed) do
|
||||
{:ok, activity} <- ActivityPub.follow(follower, followed),
|
||||
{:ok, follower, followed} <-
|
||||
User.wait_and_refresh(
|
||||
Pleroma.Config.get([:activitypub, :follow_handshake_timeout]),
|
||||
follower,
|
||||
followed
|
||||
) do
|
||||
{:ok, follower, followed, activity}
|
||||
else
|
||||
err -> err
|
||||
|
|
@ -92,7 +97,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
{:ok, object} = ActivityPub.upload(file)
|
||||
|
||||
url = List.first(object.data["url"])
|
||||
href = url["href"]
|
||||
href = url["href"] |> MediaProxy.url()
|
||||
type = url["mediaType"]
|
||||
|
||||
case format do
|
||||
|
|
@ -133,18 +138,20 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
password_confirmation: params["confirm"]
|
||||
}
|
||||
|
||||
registrations_open = Pleroma.Config.get([:instance, :registrations_open])
|
||||
|
||||
# no need to query DB if registration is open
|
||||
token =
|
||||
unless @registrations_open || is_nil(tokenString) do
|
||||
unless registrations_open || is_nil(tokenString) do
|
||||
Repo.get_by(UserInviteToken, %{token: tokenString})
|
||||
end
|
||||
|
||||
cond do
|
||||
@registrations_open || (!is_nil(token) && !token.used) ->
|
||||
registrations_open || (!is_nil(token) && !token.used) ->
|
||||
changeset = User.register_changeset(%User{}, params)
|
||||
|
||||
with {:ok, user} <- Repo.insert(changeset) do
|
||||
!@registrations_open && UserInviteToken.mark_as_used(token.token)
|
||||
!registrations_open && UserInviteToken.mark_as_used(token.token)
|
||||
{:ok, user}
|
||||
else
|
||||
{:error, changeset} ->
|
||||
|
|
@ -155,10 +162,10 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
{:error, %{error: errors}}
|
||||
end
|
||||
|
||||
!@registrations_open && is_nil(token) ->
|
||||
!registrations_open && is_nil(token) ->
|
||||
{:error, "Invalid token"}
|
||||
|
||||
!@registrations_open && token.used ->
|
||||
!registrations_open && token.used ->
|
||||
{:error, "Expired token"}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
|
||||
require Logger
|
||||
|
||||
plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])
|
||||
action_fallback(:errors)
|
||||
|
||||
def verify_credentials(%{assigns: %{user: user}} = conn, _params) do
|
||||
|
|
@ -79,7 +80,9 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("user", user)
|
||||
|
||||
activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||
activities =
|
||||
ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||
|> ActivityPub.contain_timeline(user)
|
||||
|
||||
conn
|
||||
|> render(ActivityView, "index.json", %{activities: activities, for: user})
|
||||
|
|
@ -130,6 +133,19 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
|> render(NotificationView, "notification.json", %{notifications: notifications, for: user})
|
||||
end
|
||||
|
||||
def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do
|
||||
Notification.set_read_up_to(user, latest_id)
|
||||
|
||||
notifications = Notification.for_user(user, params)
|
||||
|
||||
conn
|
||||
|> render(NotificationView, "notification.json", %{notifications: notifications, for: user})
|
||||
end
|
||||
|
||||
def notifications_read(%{assigns: %{user: user}} = conn, _) do
|
||||
bad_request_reply(conn, "You need to specify latest_id")
|
||||
end
|
||||
|
||||
def follow(%{assigns: %{user: user}} = conn, params) do
|
||||
case TwitterAPI.follow(user, params) do
|
||||
{:ok, user, followed, _activity} ->
|
||||
|
|
@ -261,7 +277,11 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
end
|
||||
|
||||
def update_avatar(%{assigns: %{user: user}} = conn, params) do
|
||||
{:ok, object} = ActivityPub.upload(params)
|
||||
upload_limit =
|
||||
Application.get_env(:pleroma, :instance)
|
||||
|> Keyword.fetch(:avatar_upload_limit)
|
||||
|
||||
{:ok, object} = ActivityPub.upload(params, upload_limit)
|
||||
change = Changeset.change(user, %{avatar: object.data})
|
||||
{:ok, user} = User.update_and_set_cache(change)
|
||||
CommonAPI.update(user)
|
||||
|
|
@ -270,7 +290,11 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
end
|
||||
|
||||
def update_banner(%{assigns: %{user: user}} = conn, params) do
|
||||
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}),
|
||||
upload_limit =
|
||||
Application.get_env(:pleroma, :instance)
|
||||
|> Keyword.fetch(:banner_upload_limit)
|
||||
|
||||
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, upload_limit),
|
||||
new_info <- Map.put(user.info, "banner", object.data),
|
||||
change <- User.info_changeset(user, %{info: new_info}),
|
||||
{:ok, user} <- User.update_and_set_cache(change) do
|
||||
|
|
@ -284,7 +308,11 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
end
|
||||
|
||||
def update_background(%{assigns: %{user: user}} = conn, params) do
|
||||
with {:ok, object} <- ActivityPub.upload(params),
|
||||
upload_limit =
|
||||
Application.get_env(:pleroma, :instance)
|
||||
|> Keyword.fetch(:background_upload_limit)
|
||||
|
||||
with {:ok, object} <- ActivityPub.upload(params, upload_limit),
|
||||
new_info <- Map.put(user.info, "background", object.data),
|
||||
change <- User.info_changeset(user, %{info: new_info}),
|
||||
{:ok, _user} <- User.update_and_set_cache(change) do
|
||||
|
|
@ -423,7 +451,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
{String.trim(name, ":"), url}
|
||||
end)
|
||||
|
||||
bio_html = CommonUtils.format_input(bio, mentions, tags)
|
||||
bio_html = CommonUtils.format_input(bio, mentions, tags, "text/plain")
|
||||
Map.put(params, "bio", bio_html |> Formatter.emojify(emoji))
|
||||
else
|
||||
params
|
||||
|
|
@ -504,6 +532,18 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
json_reply(conn, 403, json)
|
||||
end
|
||||
|
||||
def only_if_public_instance(conn = %{conn: %{assigns: %{user: _user}}}, _), do: conn
|
||||
|
||||
def only_if_public_instance(conn, _) do
|
||||
if Keyword.get(Application.get_env(:pleroma, :instance), :public) do
|
||||
conn
|
||||
else
|
||||
conn
|
||||
|> forbidden_json_reply("Invalid credentials.")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
||||
defp error_json(conn, error_message) do
|
||||
%{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
|
||||
end
|
||||
|
|
|
|||
|
|
@ -236,6 +236,10 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
|
|||
HTML.filter_tags(content, User.html_filter_policy(opts[:for]))
|
||||
|> Formatter.emojify(object["emoji"])
|
||||
|
||||
reply_parent = Activity.get_in_reply_to_activity(activity)
|
||||
|
||||
reply_user = reply_parent && User.get_cached_by_ap_id(reply_parent.actor)
|
||||
|
||||
%{
|
||||
"id" => activity.id,
|
||||
"uri" => activity.data["object"]["id"],
|
||||
|
|
@ -246,6 +250,10 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
|
|||
"is_post_verb" => true,
|
||||
"created_at" => created_at,
|
||||
"in_reply_to_status_id" => object["inReplyToStatusId"],
|
||||
"in_reply_to_screen_name" => reply_user && reply_user.nickname,
|
||||
"in_reply_to_profileurl" => User.profile_url(reply_user),
|
||||
"in_reply_to_ostatus_uri" => reply_user && reply_user.ap_id,
|
||||
"in_reply_to_user_id" => reply_user && reply_user.id,
|
||||
"statusnet_conversation_id" => conversation_id,
|
||||
"attachments" => (object["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts),
|
||||
"attentions" => attentions,
|
||||
|
|
@ -275,11 +283,11 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
|
|||
{summary, content}
|
||||
end
|
||||
|
||||
def render_content(%{"type" => "Article"} = object) do
|
||||
def render_content(%{"type" => object_type} = object) when object_type in ["Article", "Page"] do
|
||||
summary = object["name"] || object["summary"]
|
||||
|
||||
content =
|
||||
if !!summary and summary != "" do
|
||||
if !!summary and summary != "" and is_bitstring(object["url"]) do
|
||||
"<p><a href=\"#{object["url"]}\">#{summary}</a></p>#{object["content"]}"
|
||||
else
|
||||
object["content"]
|
||||
|
|
|
|||
|
|
@ -37,6 +37,13 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
|
|||
{String.trim(name, ":"), url}
|
||||
end)
|
||||
|
||||
# ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
|
||||
# For example: [{"name": "Pronoun", "value": "she/her"}, …]
|
||||
fields =
|
||||
(user.info["source_data"]["attachment"] || [])
|
||||
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
||||
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
||||
|
||||
data = %{
|
||||
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
|
||||
"description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
|
||||
|
|
@ -65,7 +72,8 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
|
|||
"is_local" => user.local,
|
||||
"locked" => !!user.info["locked"],
|
||||
"default_scope" => user.info["default_scope"] || "public",
|
||||
"no_rich_text" => user.info["no_rich_text"] || false
|
||||
"no_rich_text" => user.info["no_rich_text"] || false,
|
||||
"fields" => fields
|
||||
}
|
||||
|
||||
if assigns[:token] do
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ defmodule Pleroma.Web.WebFinger.WebFingerController do
|
|||
|
||||
alias Pleroma.Web.WebFinger
|
||||
|
||||
plug(Pleroma.Web.FederatingPlug)
|
||||
|
||||
def host_meta(conn, _params) do
|
||||
xml = WebFinger.host_meta()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,15 @@ defmodule Pleroma.Web.Websub.WebsubController do
|
|||
alias Pleroma.Web.Websub.WebsubClientSubscription
|
||||
require Logger
|
||||
|
||||
plug(
|
||||
Pleroma.Web.FederatingPlug
|
||||
when action in [
|
||||
:websub_subscription_request,
|
||||
:websub_subscription_confirmation,
|
||||
:websub_incoming
|
||||
]
|
||||
)
|
||||
|
||||
def websub_subscription_request(conn, %{"nickname" => nickname} = params) do
|
||||
user = User.get_cached_by_nickname(nickname)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue