Merge branch 'develop' into feature/new-registrations-digest

This commit is contained in:
Egor Kislitsyn 2020-02-11 13:54:55 +04:00
commit 6875ccb6bf
No known key found for this signature in database
GPG key ID: 1B49CB15B71E7805
178 changed files with 6857 additions and 1328 deletions

View file

@ -52,6 +52,9 @@ defmodule Mix.Tasks.Pleroma.Config do
defp do_migrate_to_db(config_file) do
if File.exists?(config_file) do
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
custom_config =
config_file
|> read_file()

View file

@ -0,0 +1,24 @@
defmodule Mix.Tasks.Pleroma.Email do
use Mix.Task
import Mix.Pleroma
@shortdoc "Simple Email test"
@moduledoc File.read!("docs/administration/CLI_tasks/email.md")
def run(["test" | args]) do
Mix.Pleroma.start_pleroma()
{options, [], []} =
OptionParser.parse(
args,
strict: [
to: :string
]
)
email = Pleroma.Emails.AdminEmail.test_email(options[:to])
{:ok, _} = Pleroma.Emails.Mailer.deliver(email)
shell_info("Test email has been sent to #{inspect(email.to)} from #{inspect(email.from)}")
end
end

View file

@ -9,6 +9,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
@moduledoc File.read!("docs/administration/CLI_tasks/emoji.md")
def run(["ls-packs" | args]) do
Mix.Pleroma.start_pleroma()
Application.ensure_all_started(:hackney)
{options, [], []} = parse_global_opts(args)
@ -35,6 +36,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
end
def run(["get-packs" | args]) do
Mix.Pleroma.start_pleroma()
Application.ensure_all_started(:hackney)
{options, pack_names, []} = parse_global_opts(args)

View file

@ -18,6 +18,7 @@ defmodule Mix.Tasks.Pleroma.RobotsTxt do
"""
def run(["disallow_all"]) do
Mix.Pleroma.start_pleroma()
static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/")
if !File.exists?(static_dir) do

View file

@ -30,7 +30,8 @@ defmodule Pleroma.Activity do
"Follow" => "follow",
"Announce" => "reblog",
"Like" => "favourite",
"Move" => "move"
"Move" => "move",
"EmojiReact" => "pleroma:emoji_reaction"
}
@mastodon_to_ap_notification_types for {k, v} <- @mastodon_notification_types,

View file

@ -33,6 +33,7 @@ defmodule Pleroma.Application do
def start(_type, _args) do
Pleroma.HTML.compile_scrubbers()
Pleroma.Config.DeprecationWarnings.warn()
Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled()
Pleroma.Repo.check_migrations_applied!()
setup_instrumenters()
load_custom_modules()
@ -41,12 +42,9 @@ defmodule Pleroma.Application do
children =
[
Pleroma.Repo,
Pleroma.Scheduler,
Pleroma.Config.TransferTask,
Pleroma.Emoji,
Pleroma.Captcha,
Pleroma.Daemons.ScheduledActivityDaemon,
Pleroma.Daemons.ActivityExpirationDaemon,
Pleroma.Plugs.RateLimiter.Supervisor
] ++
cachex_children() ++
@ -57,7 +55,6 @@ defmodule Pleroma.Application do
{Oban, Pleroma.Config.get(Oban)}
] ++
task_children(@env) ++
oauth_cleanup_child(oauth_cleanup_enabled?()) ++
streamer_child(@env) ++
chat_child(@env, chat_enabled?()) ++
[
@ -159,20 +156,12 @@ defmodule Pleroma.Application do
defp chat_enabled?, do: Pleroma.Config.get([:chat, :enabled])
defp oauth_cleanup_enabled?,
do: Pleroma.Config.get([:oauth2, :clean_expired_tokens], false)
defp streamer_child(:test), do: []
defp streamer_child(_) do
[Pleroma.Web.Streamer.supervisor()]
end
defp oauth_cleanup_child(true),
do: [Pleroma.Web.OAuth.Token.CleanWorker]
defp oauth_cleanup_child(_), do: []
defp chat_child(_env, true) do
[Pleroma.Web.ChatChannel.ChatChannelState]
end

View file

@ -236,15 +236,7 @@ defmodule Pleroma.ConfigDB do
end
@spec from_string(String.t()) :: atom() | no_return()
def from_string(":" <> entity), do: String.to_existing_atom(entity)
def from_string(entity) when is_binary(entity) do
if is_module_name?(entity) do
String.to_existing_atom("Elixir.#{entity}")
else
entity
end
end
def from_string(string), do: do_transform_string(string)
@spec convert(any()) :: any()
def convert(entity), do: do_convert(entity)
@ -416,7 +408,7 @@ defmodule Pleroma.ConfigDB do
@spec is_module_name?(String.t()) :: boolean()
def is_module_name?(string) do
Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth)\./, string) or
Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth|Swoosh)\./, string) or
string in ["Oban", "Ueberauth", "ExSyslogger"]
end
end

View file

@ -3,8 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.Loader do
@paths ["config/config.exs", "config/#{Mix.env()}.exs"]
@reject_keys [
Pleroma.Repo,
Pleroma.Web.Endpoint,
@ -35,8 +33,8 @@ defmodule Pleroma.Config.Loader do
def load_and_merge do
all_paths =
if Pleroma.Config.get(:release),
do: @paths ++ ["config/releases.exs"],
else: @paths
do: ["config/config.exs", "config/releases.exs"],
else: ["config/config.exs"]
all_paths
|> Enum.map(&load(&1))

View file

@ -10,6 +10,30 @@ defmodule Pleroma.Config.TransferTask do
require Logger
@type env() :: :test | :benchmark | :dev | :prod
@reboot_time_keys [
{:pleroma, :hackney_pools},
{:pleroma, :chat},
{:pleroma, Oban},
{:pleroma, :rate_limit},
{:pleroma, :markup},
{:plerome, :streamer}
]
@reboot_time_subkeys [
{:pleroma, Pleroma.Captcha, [:seconds_valid]},
{:pleroma, Pleroma.Upload, [:proxy_remote]},
{:pleroma, :instance, [:upload_limit]},
{:pleroma, :email_notifications, [:digest]},
{:pleroma, :oauth2, [:clean_expired_tokens]},
{:pleroma, Pleroma.ActivityExpiration, [:enabled]},
{:pleroma, Pleroma.ScheduledActivity, [:enabled]},
{:pleroma, :gopher, [:enabled]}
]
@reject [nil, :prometheus]
def start_link(_) do
load_and_update_env()
if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Repo)
@ -17,21 +41,34 @@ defmodule Pleroma.Config.TransferTask do
end
@spec load_and_update_env([ConfigDB.t()]) :: :ok | false
def load_and_update_env(deleted \\ []) do
def load_and_update_env(deleted \\ [], restart_pleroma? \\ true) do
with true <- Pleroma.Config.get(:configurable_from_database),
true <- Ecto.Adapters.SQL.table_exists?(Repo, "config"),
started_applications <- Application.started_applications() do
# We need to restart applications for loaded settings take effect
in_db = Repo.all(ConfigDB)
with_deleted = in_db ++ deleted
with_deleted
|> Enum.map(&merge_and_update(&1))
|> Enum.uniq()
# TODO: some problem with prometheus after restart!
|> Enum.reject(&(&1 in [:pleroma, nil, :prometheus]))
|> Enum.each(&restart(started_applications, &1))
reject_for_restart = if restart_pleroma?, do: @reject, else: [:pleroma | @reject]
applications =
with_deleted
|> Enum.map(&merge_and_update(&1))
|> Enum.uniq()
# TODO: some problem with prometheus after restart!
|> Enum.reject(&(&1 in reject_for_restart))
# to be ensured that pleroma will be restarted last
applications =
if :pleroma in applications do
List.delete(applications, :pleroma) ++ [:pleroma]
else
applications
end
Enum.each(applications, &restart(started_applications, &1, Pleroma.Config.get(:env)))
:ok
end
@ -43,12 +80,25 @@ defmodule Pleroma.Config.TransferTask do
group = ConfigDB.from_string(setting.group)
default = Pleroma.Config.Holder.config(group, key)
merged_value = merge_value(setting, default, group, key)
value = ConfigDB.from_binary(setting.value)
merged_value =
if Ecto.get_meta(setting, :state) == :deleted do
default
else
if can_be_merged?(default, value) do
ConfigDB.merge_group(group, key, default, value)
else
value
end
end
:ok = update_env(group, key, merged_value)
if group != :logger do
group
if group != :pleroma or pleroma_need_restart?(group, key, value) do
group
end
else
# change logger configuration in runtime, without restart
if Keyword.keyword?(merged_value) and
@ -76,22 +126,31 @@ defmodule Pleroma.Config.TransferTask do
end
end
defp merge_value(%{__meta__: %{state: :deleted}}, default, _group, _key), do: default
@spec pleroma_need_restart?(atom(), atom(), any()) :: boolean()
def pleroma_need_restart?(group, key, value) do
group_and_key_need_reboot?(group, key) or group_and_subkey_need_reboot?(group, key, value)
end
defp merge_value(setting, default, group, key) do
value = ConfigDB.from_binary(setting.value)
defp group_and_key_need_reboot?(group, key) do
Enum.any?(@reboot_time_keys, fn {g, k} -> g == group and k == key end)
end
if can_be_merged?(default, value) do
ConfigDB.merge_group(group, key, default, value)
else
value
end
defp group_and_subkey_need_reboot?(group, key, value) do
Keyword.keyword?(value) and
Enum.any?(@reboot_time_subkeys, fn {g, k, subkeys} ->
g == group and k == key and
Enum.any?(Keyword.keys(value), &(&1 in subkeys))
end)
end
defp update_env(group, key, nil), do: Application.delete_env(group, key)
defp update_env(group, key, value), do: Application.put_env(group, key, value)
defp restart(started_applications, app) do
defp restart(_, :pleroma, :test), do: Logger.warn("pleroma restarted")
defp restart(_, :pleroma, _), do: send(Restarter.Pleroma, :after_boot)
defp restart(started_applications, app, _) do
with {^app, _, _} <- List.keyfind(started_applications, app, 0),
:ok <- Application.stop(app) do
:ok = Application.start(app)

View file

@ -1,66 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Daemons.ActivityExpirationDaemon do
alias Pleroma.Activity
alias Pleroma.ActivityExpiration
alias Pleroma.Config
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.CommonAPI
require Logger
use GenServer
import Ecto.Query
@schedule_interval :timer.minutes(1)
def start_link(_) do
GenServer.start_link(__MODULE__, nil)
end
@impl true
def init(_) do
if Config.get([ActivityExpiration, :enabled]) do
schedule_next()
{:ok, nil}
else
:ignore
end
end
def perform(:execute, expiration_id) do
try do
expiration =
ActivityExpiration
|> where([e], e.id == ^expiration_id)
|> Repo.one!()
activity = Activity.get_by_id_with_object(expiration.activity_id)
user = User.get_by_ap_id(activity.object.data["actor"])
CommonAPI.delete(activity.id, user)
rescue
error ->
Logger.error("#{__MODULE__} Couldn't delete expired activity: #{inspect(error)}")
end
end
@impl true
def handle_info(:perform, state) do
ActivityExpiration.due_expirations(@schedule_interval)
|> Enum.each(fn expiration ->
Pleroma.Workers.ActivityExpirationWorker.enqueue(
"activity_expiration",
%{"activity_expiration_id" => expiration.id}
)
end)
schedule_next()
{:noreply, state}
end
defp schedule_next do
Process.send_after(self(), :perform, @schedule_interval)
end
end

View file

@ -1,42 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Daemons.DigestEmailDaemon do
alias Pleroma.Repo
alias Pleroma.Workers.DigestEmailsWorker
import Ecto.Query
def perform do
config = Pleroma.Config.get([:email_notifications, :digest])
negative_interval = -Map.fetch!(config, :interval)
inactivity_threshold = Map.fetch!(config, :inactivity_threshold)
inactive_users_query = Pleroma.User.list_inactive_users_query(inactivity_threshold)
now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
from(u in inactive_users_query,
where: fragment(~s(? ->'digest' @> 'true'), u.email_notifications),
where: u.last_digest_emailed_at < datetime_add(^now, ^negative_interval, "day"),
select: u
)
|> Repo.all()
|> Enum.each(fn user ->
DigestEmailsWorker.enqueue("digest_email", %{"user_id" => user.id})
end)
end
@doc """
Send digest email to the given user.
Updates `last_digest_emailed_at` field for the user and returns the updated user.
"""
@spec perform(Pleroma.User.t()) :: Pleroma.User.t()
def perform(user) do
with %Swoosh.Email{} = email <- Pleroma.Emails.UserEmail.digest_email(user) do
Pleroma.Emails.Mailer.deliver_async(email)
end
Pleroma.User.touch_last_digest_emailed_at(user)
end
end

View file

@ -1,62 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Daemons.ScheduledActivityDaemon do
@moduledoc """
Sends scheduled activities to the job queue.
"""
alias Pleroma.Config
alias Pleroma.ScheduledActivity
alias Pleroma.User
alias Pleroma.Web.CommonAPI
use GenServer
require Logger
@schedule_interval :timer.minutes(1)
def start_link(_) do
GenServer.start_link(__MODULE__, nil)
end
def init(_) do
if Config.get([ScheduledActivity, :enabled]) do
schedule_next()
{:ok, nil}
else
:ignore
end
end
def perform(:execute, scheduled_activity_id) do
try do
{:ok, scheduled_activity} = ScheduledActivity.delete(scheduled_activity_id)
%User{} = user = User.get_cached_by_id(scheduled_activity.user_id)
{:ok, _result} = CommonAPI.post(user, scheduled_activity.params)
rescue
error ->
Logger.error(
"#{__MODULE__} Couldn't create a status from the scheduled activity: #{inspect(error)}"
)
end
end
def handle_info(:perform, state) do
ScheduledActivity.due_activities(@schedule_interval)
|> Enum.each(fn scheduled_activity ->
Pleroma.Workers.ScheduledActivityWorker.enqueue(
"execute",
%{"activity_id" => scheduled_activity.id}
)
end)
schedule_next()
{:noreply, state}
end
defp schedule_next do
Process.send_after(self(), :perform, @schedule_interval)
end
end

View file

@ -13,7 +13,7 @@ defmodule Pleroma.Docs.Generator do
|> Enum.filter(&String.ends_with?(&1, ".ex"))
|> Enum.map(fn filename ->
module = filename |> String.trim_trailing(".ex") |> Macro.camelize()
String.to_existing_atom(start <> module)
String.to_atom(start <> module)
end)
end
end

View file

@ -7,6 +7,7 @@ defmodule Pleroma.Emails.AdminEmail do
import Swoosh.Email
alias Pleroma.Config
alias Pleroma.Web.Router.Helpers
defp instance_config, do: Pleroma.Config.get(:instance)
@ -17,7 +18,20 @@ defmodule Pleroma.Emails.AdminEmail do
end
defp user_url(user) do
Helpers.feed_url(Pleroma.Web.Endpoint, :feed_redirect, user.id)
Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, user.id)
end
def test_email(mail_to \\ nil) do
html_body = """
<h3>Instance Test Email</h3>
<p>A test email was requested. Hello. :)</p>
"""
new()
|> to(mail_to || Config.get([:instance, :email]))
|> from({instance_name(), instance_notify_email()})
|> subject("Instance Test Email")
|> html_body(html_body)
end
def report(to, reporter, account, statuses, comment) do

View file

@ -58,8 +58,8 @@ defmodule Pleroma.FollowingRelationship do
def unfollow(%User{} = follower, %User{} = following) do
case get(follower, following) do
nil -> {:ok, nil}
%__MODULE__{} = following_relationship -> Repo.delete(following_relationship)
_ -> {:ok, nil}
end
end

View file

@ -13,7 +13,8 @@ defmodule Pleroma.Formatter do
@auto_linker_config hashtag: true,
hashtag_handler: &Pleroma.Formatter.hashtag_handler/4,
mention: true,
mention_handler: &Pleroma.Formatter.mention_handler/4
mention_handler: &Pleroma.Formatter.mention_handler/4,
scheme: true
def escape_mention_handler("@" <> nickname = mention, buffer, _, _) do
case User.get_cached_by_nickname(nickname) do

View file

@ -294,7 +294,7 @@ defmodule Pleroma.Notification do
end
def create_notifications(%Activity{data: %{"type" => type}} = activity)
when type in ["Like", "Announce", "Follow", "Move"] do
when type in ["Like", "Announce", "Follow", "Move", "EmojiReact"] do
notifications =
activity
|> get_notified_from_activity()
@ -322,7 +322,7 @@ defmodule Pleroma.Notification do
def get_notified_from_activity(activity, local_only \\ true)
def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
when type in ["Create", "Like", "Announce", "Follow", "Move"] do
when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReact"] do
[]
|> Utils.maybe_notify_to_recipients(activity)
|> Utils.maybe_notify_mentioned_recipients(activity)

View file

@ -184,11 +184,14 @@ defmodule Pleroma.Object do
with {:ok, _obj} = swap_object_with_tombstone(object),
deleted_activity = Activity.delete_all_by_object_ap_id(id),
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
{:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path),
{:ok, _} <-
Pleroma.Workers.AttachmentsCleanupWorker.enqueue("cleanup_attachments", %{
"object" => object
}) do
{:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
with true <- Pleroma.Config.get([:instance, :cleanup_attachments]) do
{:ok, _} =
Pleroma.Workers.AttachmentsCleanupWorker.enqueue("cleanup_attachments", %{
"object" => object
})
end
{:ok, object, deleted_activity}
end
end

View file

@ -117,6 +117,9 @@ defmodule Pleroma.Object.Fetcher do
{:error, %Tesla.Mock.Error{}} ->
nil
{:error, "Object has been deleted"} ->
nil
e ->
Logger.error("Error while fetching #{id}: #{inspect(e)}")
nil

View file

@ -6,6 +6,8 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
alias Pleroma.Config
import Plug.Conn
require Logger
def init(opts), do: opts
def call(conn, _options) do
@ -90,6 +92,51 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|> Enum.join("; ")
end
def warn_if_disabled do
unless Config.get([:http_security, :enabled]) do
Logger.warn("
.i;;;;i.
iYcviii;vXY:
.YXi .i1c.
.YC. . in7.
.vc. ...... ;1c.
i7, .. .;1;
i7, .. ... .Y1i
,7v .6MMM@; .YX,
.7;. ..IMMMMMM1 :t7.
.;Y. ;$MMMMMM9. :tc.
vY. .. .nMMM@MMU. ;1v.
i7i ... .#MM@M@C. .....:71i
it: .... $MMM@9;.,i;;;i,;tti
:t7. ..... 0MMMWv.,iii:::,,;St.
.nC. ..... IMMMQ..,::::::,.,czX.
.ct: ....... .ZMMMI..,:::::::,,:76Y.
c2: ......,i..Y$M@t..:::::::,,..inZY
vov ......:ii..c$MBc..,,,,,,,,,,..iI9i
i9Y ......iii:..7@MA,..,,,,,,,,,....;AA:
iIS. ......:ii::..;@MI....,............;Ez.
.I9. ......:i::::...8M1..................C0z.
.z9; ......:i::::,.. .i:...................zWX.
vbv ......,i::::,,. ................. :AQY
c6Y. .,...,::::,,..:t0@@QY. ................ :8bi
:6S. ..,,...,:::,,,..EMMMMMMI. ............... .;bZ,
:6o, .,,,,..:::,,,..i#MMMMMM#v................. YW2.
.n8i ..,,,,,,,::,,,,.. tMMMMM@C:.................. .1Wn
7Uc. .:::,,,,,::,,,,.. i1t;,..................... .UEi
7C...::::::::::::,,,,.. .................... vSi.
;1;...,,::::::,......... .................. Yz:
v97,......... .voC.
izAotX7777777777777777777777777777777777777777Y7n92:
.;CoIIIIIUAA666666699999ZZZZZZZZZZZZZZZZZZZZ6ov.
HTTP Security is disabled. Please re-enable it to prevent users from attacking
your instance and your users via malicious posts:
config :pleroma, :http_security, enabled: true
")
end
end
defp maybe_send_sts_header(conn, true) do
max_age_sts = Config.get([:http_security, :sts_max_age])
max_age_ct = Config.get([:http_security, :ct_max_age])

View file

@ -1,21 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.Parsers do
@moduledoc "Initializes Plug.Parsers with upload limit set at boot time"
@behaviour Plug
def init(_opts) do
Plug.Parsers.init(
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
json_decoder: Jason,
length: Pleroma.Config.get([:instance, :upload_limit]),
body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
)
end
defdelegate call(conn, opts), to: Plug.Parsers
end

View file

@ -67,6 +67,8 @@ defmodule Pleroma.Plugs.RateLimiter do
alias Pleroma.Plugs.RateLimiter.LimiterSupervisor
alias Pleroma.User
require Logger
def init(opts) do
limiter_name = Keyword.get(opts, :name)
@ -89,18 +91,39 @@ defmodule Pleroma.Plugs.RateLimiter do
def call(conn, nil), do: conn
def call(conn, settings) do
settings
|> incorporate_conn_info(conn)
|> check_rate()
|> case do
{:ok, _count} ->
case disabled?() do
true ->
if Pleroma.Config.get(:env) == :prod,
do: Logger.warn("Rate limiter is disabled for localhost/socket")
conn
{:error, _count} ->
render_throttled_error(conn)
false ->
settings
|> incorporate_conn_info(conn)
|> check_rate()
|> case do
{:ok, _count} ->
conn
{:error, _count} ->
render_throttled_error(conn)
end
end
end
def disabled? do
localhost_or_socket =
Pleroma.Config.get([Pleroma.Web.Endpoint, :http, :ip])
|> Tuple.to_list()
|> Enum.join(".")
|> String.match?(~r/^local|^127.0.0.1/)
remote_ip_disabled = not Pleroma.Config.get([Pleroma.Plugs.RemoteIp, :enabled])
localhost_or_socket and remote_ip_disabled
end
def inspect_bucket(conn, name_root, settings) do
settings =
settings

View file

@ -10,10 +10,7 @@ defmodule Pleroma.Plugs.RemoteIp do
@behaviour Plug
@headers ~w[
forwarded
x-forwarded-for
x-client-ip
x-real-ip
]
# https://en.wikipedia.org/wiki/Localhost

View file

@ -11,11 +11,9 @@ defmodule Pleroma.Plugs.UserEnabledPlug do
end
def call(%{assigns: %{user: %User{} = user}} = conn, _) do
if User.auth_active?(user) do
conn
else
conn
|> assign(:user, nil)
case User.account_status(user) do
:active -> conn
_ -> assign(conn, :user, nil)
end
end

View file

@ -5,15 +5,19 @@
defmodule Pleroma.ScheduledActivity do
use Ecto.Schema
alias Ecto.Multi
alias Pleroma.Config
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Workers.ScheduledActivityWorker
import Ecto.Query
import Ecto.Changeset
@type t :: %__MODULE__{}
@min_offset :timer.minutes(5)
schema "scheduled_activities" do
@ -105,16 +109,32 @@ defmodule Pleroma.ScheduledActivity do
end
def new(%User{} = user, attrs) do
%ScheduledActivity{user_id: user.id}
|> changeset(attrs)
changeset(%ScheduledActivity{user_id: user.id}, attrs)
end
@doc """
Creates ScheduledActivity and add to queue to perform at scheduled_at date
"""
@spec create(User.t(), map()) :: {:ok, ScheduledActivity.t()} | {:error, Ecto.Changeset.t()}
def create(%User{} = user, attrs) do
user
|> new(attrs)
|> Repo.insert()
Multi.new()
|> Multi.insert(:scheduled_activity, new(user, attrs))
|> maybe_add_jobs(Config.get([ScheduledActivity, :enabled]))
|> Repo.transaction()
|> transaction_response
end
defp maybe_add_jobs(multi, true) do
multi
|> Multi.run(:scheduled_activity_job, fn _repo, %{scheduled_activity: activity} ->
%{activity_id: activity.id}
|> ScheduledActivityWorker.new(scheduled_at: activity.scheduled_at)
|> Oban.insert()
end)
end
defp maybe_add_jobs(multi, _), do: multi
def get(%User{} = user, scheduled_activity_id) do
ScheduledActivity
|> where(user_id: ^user.id)
@ -122,25 +142,43 @@ defmodule Pleroma.ScheduledActivity do
|> Repo.one()
end
def update(%ScheduledActivity{} = scheduled_activity, attrs) do
scheduled_activity
|> update_changeset(attrs)
|> Repo.update()
@spec update(ScheduledActivity.t(), map()) ::
{:ok, ScheduledActivity.t()} | {:error, Ecto.Changeset.t()}
def update(%ScheduledActivity{id: id} = scheduled_activity, attrs) do
with {:error, %Ecto.Changeset{valid?: true} = changeset} <-
{:error, update_changeset(scheduled_activity, attrs)} do
Multi.new()
|> Multi.update(:scheduled_activity, changeset)
|> Multi.update_all(:scheduled_job, job_query(id),
set: [scheduled_at: get_field(changeset, :scheduled_at)]
)
|> Repo.transaction()
|> transaction_response
end
end
def delete(%ScheduledActivity{} = scheduled_activity) do
scheduled_activity
|> Repo.delete()
@doc "Deletes a ScheduledActivity and linked jobs."
@spec delete(ScheduledActivity.t() | binary() | integer) ::
{:ok, ScheduledActivity.t()} | {:error, Ecto.Changeset.t()}
def delete(%ScheduledActivity{id: id} = scheduled_activity) do
Multi.new()
|> Multi.delete(:scheduled_activity, scheduled_activity, stale_error_field: :id)
|> Multi.delete_all(:jobs, job_query(id))
|> Repo.transaction()
|> transaction_response
end
def delete(id) when is_binary(id) or is_integer(id) do
ScheduledActivity
|> where(id: ^id)
|> select([sa], sa)
|> Repo.delete_all()
|> case do
{1, [scheduled_activity]} -> {:ok, scheduled_activity}
_ -> :error
delete(%__MODULE__{id: id})
end
defp transaction_response(result) do
case result do
{:ok, %{scheduled_activity: scheduled_activity}} ->
{:ok, scheduled_activity}
{:error, _, changeset, _} ->
{:error, changeset}
end
end
@ -158,4 +196,11 @@ defmodule Pleroma.ScheduledActivity do
|> where([sa], sa.scheduled_at < ^naive_datetime)
|> Repo.all()
end
def job_query(scheduled_activity_id) do
from(j in Oban.Job,
where: j.queue == "scheduled_activities",
where: fragment("args ->> 'activity_id' = ?::text", ^to_string(scheduled_activity_id))
)
end
end

View file

@ -1,7 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Scheduler do
use Quantum.Scheduler, otp_app: :pleroma
end

View file

@ -9,22 +9,43 @@ defmodule Pleroma.Stats do
use GenServer
@interval 1000 * 60 * 60
@init_state %{
peers: [],
stats: %{
domain_count: 0,
status_count: 0,
user_count: 0
}
}
def start_link(_) do
GenServer.start_link(__MODULE__, initial_data(), name: __MODULE__)
GenServer.start_link(
__MODULE__,
@init_state,
name: __MODULE__
)
end
@doc "Performs update stats"
def force_update do
GenServer.call(__MODULE__, :force_update)
end
@doc "Performs collect stats"
def do_collect do
GenServer.cast(__MODULE__, :run_update)
end
@doc "Returns stats data"
@spec get_stats() :: %{domain_count: integer(), status_count: integer(), user_count: integer()}
def get_stats do
%{stats: stats} = GenServer.call(__MODULE__, :get_state)
stats
end
@doc "Returns list peers"
@spec get_peers() :: list(String.t())
def get_peers do
%{peers: peers} = GenServer.call(__MODULE__, :get_state)
@ -32,7 +53,6 @@ defmodule Pleroma.Stats do
end
def init(args) do
Process.send(self(), :run_update, [])
{:ok, args}
end
@ -45,17 +65,12 @@ defmodule Pleroma.Stats do
{:reply, state, state}
end
def handle_info(:run_update, _state) do
def handle_cast(:run_update, _state) do
new_stats = get_stat_data()
Process.send_after(self(), :run_update, @interval)
{:noreply, new_stats}
end
defp initial_data do
%{peers: [], stats: %{}}
end
defp get_stat_data do
peers =
from(
@ -74,7 +89,11 @@ defmodule Pleroma.Stats do
%{
peers: peers,
stats: %{domain_count: domain_count, status_count: status_count, user_count: user_count}
stats: %{
domain_count: domain_count,
status_count: status_count,
user_count: user_count
}
}
end
end

View file

@ -12,6 +12,7 @@ defmodule Pleroma.User do
alias Comeonin.Pbkdf2
alias Ecto.Multi
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Conversation.Participation
alias Pleroma.Delivery
alias Pleroma.FollowingRelationship
@ -35,7 +36,7 @@ defmodule Pleroma.User do
require Logger
@type t :: %__MODULE__{}
@type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
@ -216,14 +217,21 @@ defmodule Pleroma.User do
end
end
@doc "Returns if the user should be allowed to authenticate"
def auth_active?(%User{deactivated: true}), do: false
@doc "Returns status account"
@spec account_status(User.t()) :: account_status()
def account_status(%User{deactivated: true}), do: :deactivated
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
def auth_active?(%User{confirmation_pending: true}),
do: !Pleroma.Config.get([:instance, :account_activation_required])
def account_status(%User{confirmation_pending: true}) do
case Config.get([:instance, :account_activation_required]) do
true -> :confirmation_pending
_ -> :active
end
end
def auth_active?(%User{}), do: true
def account_status(%User{}), do: :active
@spec visible_for?(User.t(), User.t() | nil) :: boolean()
def visible_for?(user, for_user \\ nil)
def visible_for?(%User{invisible: true}, _), do: false
@ -231,15 +239,17 @@ defmodule Pleroma.User do
def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
def visible_for?(%User{} = user, for_user) do
auth_active?(user) || superuser?(for_user)
account_status(user) == :active || superuser?(for_user)
end
def visible_for?(_, _), do: false
@spec superuser?(User.t()) :: boolean()
def superuser?(%User{local: true, is_admin: true}), do: true
def superuser?(%User{local: true, is_moderator: true}), do: true
def superuser?(_), do: false
@spec invisible?(User.t()) :: boolean()
def invisible?(%User{invisible: true}), do: true
def invisible?(_), do: false
@ -637,25 +647,48 @@ defmodule Pleroma.User do
end
end
def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
{:error, "Not subscribed!"}
end
def unfollow(%User{} = follower, %User{} = followed) do
if following?(follower, followed) and follower.ap_id != followed.ap_id do
FollowingRelationship.unfollow(follower, followed)
case get_follow_state(follower, followed) do
state when state in ["accept", "pending"] ->
FollowingRelationship.unfollow(follower, followed)
{:ok, followed} = update_follower_count(followed)
{:ok, followed} = update_follower_count(followed)
{:ok, follower} =
follower
|> update_following_count()
|> set_cache()
{:ok, follower} =
follower
|> update_following_count()
|> set_cache()
{:ok, follower, Utils.fetch_latest_follow(follower, followed)}
{:ok, follower, Utils.fetch_latest_follow(follower, followed)}
else
{:error, "Not subscribed!"}
nil ->
{:error, "Not subscribed!"}
end
end
defdelegate following?(follower, followed), to: FollowingRelationship
def get_follow_state(%User{} = follower, %User{} = following) do
following_relationship = FollowingRelationship.get(follower, following)
case {following_relationship, following.local} do
{nil, false} ->
case Utils.fetch_latest_follow(follower, following) do
%{data: %{"state" => state}} when state in ["pending", "accept"] -> state
_ -> nil
end
{%{state: state}, _} ->
state
{nil, _} ->
nil
end
end
def locked?(%User{} = user) do
user.locked || false
end
@ -1502,7 +1535,7 @@ defmodule Pleroma.User do
data
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|> remote_user_creation()
|> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
|> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
|> set_cache()
end

View file

@ -58,7 +58,7 @@ defmodule Pleroma.UserRelationship do
target_id: target.id
})
|> Repo.insert(
on_conflict: :replace_all_except_primary_key,
on_conflict: {:replace_all_except, [:id]},
conflict_target: [:source_id, :relationship_type, :target_id]
)
end

View file

@ -325,12 +325,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def react_with_emoji(user, object, emoji, options \\ []) do
with local <- Keyword.get(options, :local, true),
activity_id <- Keyword.get(options, :activity_id, nil),
Pleroma.Emoji.is_unicode_emoji?(emoji),
true <- Pleroma.Emoji.is_unicode_emoji?(emoji),
reaction_data <- make_emoji_reaction_data(user, object, emoji, activity_id),
{:ok, activity} <- insert(reaction_data, local),
{:ok, object} <- add_emoji_reaction_to_object(activity, object),
:ok <- maybe_federate(activity) do
{:ok, activity, object}
else
e -> {:error, e}
end
end
@ -345,6 +347,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:ok, object} <- remove_emoji_reaction_from_object(reaction_activity, object),
:ok <- maybe_federate(activity) do
{:ok, activity, object}
else
e -> {:error, e}
end
end
@ -728,7 +732,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
params
|> Map.put("user", reading_user)
|> Map.put("actor_id", user.ap_id)
|> Map.put("whole_db", true)
recipients =
user_activities_recipients(%{
@ -746,7 +749,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> Map.put("type", ["Create", "Announce"])
|> Map.put("user", reading_user)
|> Map.put("actor_id", user.ap_id)
|> Map.put("whole_db", true)
|> Map.put("pinned_activity_ids", user.pinned_activities)
params =
@ -773,7 +775,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
params
|> Map.put("type", ["Create", "Announce"])
|> Map.put("instance", params["instance"])
|> Map.put("whole_db", true)
fetch_activities([Pleroma.Constants.as_public()], params, :offset)
|> Enum.reverse()
@ -1369,6 +1370,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
data <- maybe_update_follow_information(data) do
{:ok, data}
else
{:error, "Object has been deleted"} = e ->
Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
{:error, e}
e ->
Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
{:error, e}

View file

@ -580,7 +580,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"star" => ""
}
@doc "Rewrite misskey likes into EmojiReactions"
@doc "Rewrite misskey likes into EmojiReacts"
def handle_incoming(
%{
"type" => "Like",
@ -589,7 +589,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
options
) do
data
|> Map.put("type", "EmojiReaction")
|> Map.put("type", "EmojiReact")
|> Map.put("content", @misskey_reactions[reaction] || reaction)
|> handle_incoming(options)
end
@ -610,7 +610,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def handle_incoming(
%{
"type" => "EmojiReaction",
"type" => "EmojiReact",
"object" => object_id,
"actor" => _actor,
"id" => id,
@ -751,7 +751,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def handle_incoming(
%{
"type" => "Undo",
"object" => %{"type" => "EmojiReaction", "id" => reaction_activity_id},
"object" => %{"type" => "EmojiReact", "id" => reaction_activity_id},
"actor" => _actor,
"id" => id
} = data,

View file

@ -308,7 +308,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def make_emoji_reaction_data(user, object, emoji, activity_id) do
make_like_data(user, object, activity_id)
|> Map.put("type", "EmojiReaction")
|> Map.put("type", "EmojiReact")
|> Map.put("content", emoji)
end
@ -337,7 +337,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
%Activity{data: %{"content" => emoji, "actor" => actor}},
object
) do
reactions = object.data["reactions"] || []
reactions = get_cached_emoji_reactions(object)
new_reactions =
case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do
@ -365,7 +365,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
%Activity{data: %{"content" => emoji, "actor" => actor}},
object
) do
reactions = object.data["reactions"] || []
reactions = get_cached_emoji_reactions(object)
new_reactions =
case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do
@ -385,6 +385,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do
update_element_in_object("reaction", new_reactions, object, count)
end
def get_cached_emoji_reactions(object) do
if is_list(object.data["reactions"]) do
object.data["reactions"]
else
[]
end
end
@spec add_like_to_object(Activity.t(), Object.t()) ::
{:ok, Object.t()} | {:error, Ecto.Changeset.t()}
def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do
@ -482,10 +490,19 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Repo.one()
end
def fetch_latest_undo(%User{ap_id: ap_id}) do
"Undo"
|> Activity.Queries.by_type()
|> where(actor: ^ap_id)
|> order_by([activity], fragment("? desc nulls last", activity.id))
|> limit(1)
|> Repo.one()
end
def get_latest_reaction(internal_activity_id, %{ap_id: ap_id}, emoji) do
%{data: %{"object" => object_ap_id}} = Activity.get_by_id(internal_activity_id)
"EmojiReaction"
"EmojiReact"
|> Activity.Queries.by_type()
|> where(actor: ^ap_id)
|> where([activity], fragment("?->>'content' = ?", activity.data, ^emoji))

View file

@ -97,7 +97,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
plug(
OAuthScopesPlug,
%{scopes: ["read"], admin: true}
when action in [:config_show, :migrate_from_db, :list_log]
when action in [:config_show, :list_log]
)
plug(
@ -793,33 +793,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|> Plug.Conn.send_resp(200, @descriptions_json)
end
def migrate_from_db(conn, _params) do
with :ok <- configurable_from_database(conn) do
Mix.Tasks.Pleroma.Config.run([
"migrate_from_db",
"--env",
to_string(Pleroma.Config.get(:env)),
"-d"
])
json(conn, %{})
end
end
def config_show(conn, %{"only_db" => true}) do
with :ok <- configurable_from_database(conn) do
configs = Pleroma.Repo.all(ConfigDB)
if configs == [] do
errors(
conn,
{:error, "To use configuration from database migrate your settings to database."}
)
else
conn
|> put_view(ConfigView)
|> render("index.json", %{configs: configs})
end
conn
|> put_view(ConfigView)
|> render("index.json", %{configs: configs})
end
end
@ -827,45 +807,38 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
with :ok <- configurable_from_database(conn) do
configs = ConfigDB.get_all_as_keyword()
if configs == [] do
errors(
conn,
{:error, "To use configuration from database migrate your settings to database."}
)
else
merged =
Pleroma.Config.Holder.config()
|> ConfigDB.merge(configs)
|> Enum.map(fn {group, values} ->
Enum.map(values, fn {key, value} ->
db =
if configs[group][key] do
ConfigDB.get_db_keys(configs[group][key], key)
end
merged =
Pleroma.Config.Holder.config()
|> ConfigDB.merge(configs)
|> Enum.map(fn {group, values} ->
Enum.map(values, fn {key, value} ->
db =
if configs[group][key] do
ConfigDB.get_db_keys(configs[group][key], key)
end
db_value = configs[group][key]
db_value = configs[group][key]
merged_value =
if !is_nil(db_value) and Keyword.keyword?(db_value) and
ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
ConfigDB.merge_group(group, key, value, db_value)
else
value
end
merged_value =
if !is_nil(db_value) and Keyword.keyword?(db_value) and
ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
ConfigDB.merge_group(group, key, value, db_value)
else
value
end
setting = %{
group: ConfigDB.convert(group),
key: ConfigDB.convert(key),
value: ConfigDB.convert(merged_value)
}
setting = %{
group: ConfigDB.convert(group),
key: ConfigDB.convert(key),
value: ConfigDB.convert(merged_value)
}
if db, do: Map.put(setting, :db, db), else: setting
end)
if db, do: Map.put(setting, :db, db), else: setting
end)
|> List.flatten()
end)
|> List.flatten()
json(conn, %{configs: merged})
end
json(conn, %{configs: merged})
end
end
@ -890,17 +863,36 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
Ecto.get_meta(config, :state) == :deleted
end)
Pleroma.Config.TransferTask.load_and_update_env(deleted)
Pleroma.Config.TransferTask.load_and_update_env(deleted, false)
Mix.Tasks.Pleroma.Config.run([
"migrate_from_db",
"--env",
to_string(Pleroma.Config.get(:env))
])
need_reboot? =
Enum.any?(updated, fn config ->
group = ConfigDB.from_string(config.group)
key = ConfigDB.from_string(config.key)
value = ConfigDB.from_binary(config.value)
Pleroma.Config.TransferTask.pleroma_need_restart?(group, key, value)
end)
response = %{configs: updated}
response =
if need_reboot?, do: Map.put(response, :need_reboot, need_reboot?), else: response
conn
|> put_view(ConfigView)
|> render("index.json", %{configs: updated})
|> render("index.json", response)
end
end
def restart(conn, _params) do
with :ok <- configurable_from_database(conn) do
if Pleroma.Config.get(:env) == :test do
Logger.warn("pleroma restarted")
else
send(Restarter.Pleroma, {:restart, 50})
end
json(conn, %{})
end
end

View file

@ -5,10 +5,16 @@
defmodule Pleroma.Web.AdminAPI.ConfigView do
use Pleroma.Web, :view
def render("index.json", %{configs: configs}) do
%{
def render("index.json", %{configs: configs} = params) do
map = %{
configs: render_many(configs, __MODULE__, "show.json", as: :config)
}
if params[:need_reboot] do
Map.put(map, :need_reboot, true)
else
map
end
end
def render("show.json", %{config: config}) do

View file

@ -315,8 +315,9 @@ defmodule Pleroma.Web.CommonAPI do
with %Activity{
actor: ^user_ap_id,
data: %{"type" => "Create"},
object: %Object{data: %{"type" => "Note"}}
object: %Object{data: %{"type" => object_type}}
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
true <- object_type in ["Note", "Article", "Question"],
true <- Visibility.is_public?(activity),
{:ok, _user} <- User.add_pinnned_activity(user, activity) do
{:ok, activity}

View file

@ -179,9 +179,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end)
end_time =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(expires_in)
|> NaiveDateTime.to_iso8601()
DateTime.utc_now()
|> DateTime.add(expires_in)
|> DateTime.to_iso8601()
key = if truthy_param?(data["poll"]["multiple"]), do: "anyOf", else: "oneOf"
poll = %{"type" => "Question", key => option_notes, "closed" => end_time}

View file

@ -76,8 +76,7 @@ defmodule Pleroma.Web.ControllerHelper do
end
end
def try_render(conn, target, params)
when is_binary(target) do
def try_render(conn, target, params) when is_binary(target) do
case render(conn, target, params) do
nil -> render_error(conn, :not_implemented, "Can't display this activity")
res -> res
@ -87,4 +86,8 @@ defmodule Pleroma.Web.ControllerHelper do
def try_render(conn, _, _) do
render_error(conn, :not_implemented, "Can't display this activity")
end
@spec put_in_if_exist(map(), atom() | String.t(), any) :: map()
def put_in_if_exist(map, _key, nil), do: map
def put_in_if_exist(map, key, value), do: put_in(map, key, value)
end

View file

@ -61,7 +61,17 @@ defmodule Pleroma.Web.Endpoint do
plug(Plug.RequestId)
plug(Plug.Logger, log: :debug)
plug(Pleroma.Plugs.Parsers)
plug(Plug.Parsers,
parsers: [
:urlencoded,
{:multipart, length: {Pleroma.Config, :get, [[:instance, :upload_limit]]}},
:json
],
pass: ["*/*"],
json_decoder: Jason,
length: Pleroma.Config.get([:instance, :upload_limit]),
body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
)
plug(Plug.MethodOverride)
plug(Plug.Head)

View file

@ -13,21 +13,53 @@ defmodule Pleroma.Web.Feed.FeedView do
require Pleroma.Constants
def prepare_activity(activity) do
@spec pub_date(String.t() | DateTime.t()) :: String.t()
def pub_date(date) when is_binary(date) do
date
|> Timex.parse!("{ISO:Extended}")
|> pub_date
end
def pub_date(%DateTime{} = date), do: Timex.format!(date, "{RFC822}")
def prepare_activity(activity, opts \\ []) do
object = activity_object(activity)
actor =
if opts[:actor] do
Pleroma.User.get_cached_by_ap_id(activity.actor)
end
%{
activity: activity,
data: Map.get(object, :data),
object: object
object: object,
actor: actor
}
end
def most_recent_update(activities) do
with %{updated_at: updated_at} <- List.first(activities) do
NaiveDateTime.to_iso8601(updated_at)
end
end
def most_recent_update(activities, user) do
(List.first(activities) || user).updated_at
|> NaiveDateTime.to_iso8601()
end
def feed_logo do
case Pleroma.Config.get([:feed, :logo]) do
nil ->
"#{Pleroma.Web.base_url()}/static/logo.png"
logo ->
"#{Pleroma.Web.base_url()}#{logo}"
end
|> MediaProxy.url()
end
def logo(user) do
user
|> User.avatar_url()
@ -40,6 +72,8 @@ defmodule Pleroma.Web.Feed.FeedView do
def activity_title(%{data: %{"content" => content}}, opts \\ %{}) do
content
|> Pleroma.Web.Metadata.Utils.scrub_html()
|> Pleroma.Emoji.Formatter.demojify()
|> Formatter.truncate(opts[:max_length], opts[:omission])
|> escape()
end
@ -50,6 +84,8 @@ defmodule Pleroma.Web.Feed.FeedView do
|> escape()
end
def activity_content(_), do: ""
def activity_context(activity), do: activity.data["context"]
def attachment_href(attachment) do

View file

@ -0,0 +1,41 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Feed.TagController do
use Pleroma.Web, :controller
alias Pleroma.Config
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.Feed.FeedView
import Pleroma.Web.ControllerHelper, only: [put_in_if_exist: 3]
def feed(conn, %{"tag" => raw_tag} = params) do
{format, tag} = parse_tag(raw_tag)
activities =
%{"type" => ["Create"], "tag" => tag}
|> put_in_if_exist("max_id", params["max_id"])
|> ActivityPub.fetch_public_activities()
conn
|> put_resp_content_type("application/atom+xml")
|> put_view(FeedView)
|> render("tag.#{format}",
activities: activities,
tag: tag,
feed_config: Config.get([:feed])
)
end
@spec parse_tag(binary() | any()) :: {format :: String.t(), tag :: String.t()}
defp parse_tag(raw_tag) when is_binary(raw_tag) do
case Enum.reverse(String.split(raw_tag, ".")) do
[format | tag] when format in ["atom", "rss"] -> {format, Enum.join(tag, ".")}
_ -> {"rss", raw_tag}
end
end
defp parse_tag(raw_tag), do: {"rss", raw_tag}
end

View file

@ -2,13 +2,16 @@
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Feed.FeedController do
defmodule Pleroma.Web.Feed.UserController do
use Pleroma.Web, :controller
alias Fallback.RedirectController
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.Feed.FeedView
import Pleroma.Web.ControllerHelper, only: [put_in_if_exist: 3]
plug(Pleroma.Plugs.SetFormatPlug when action in [:feed_redirect])
@ -27,7 +30,7 @@ defmodule Pleroma.Web.Feed.FeedController do
def feed_redirect(conn, %{"nickname" => nickname}) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
redirect(conn, external: "#{feed_url(conn, :feed, user.nickname)}.atom")
redirect(conn, external: "#{user_feed_url(conn, :feed, user.nickname)}.atom")
end
end
@ -36,15 +39,15 @@ defmodule Pleroma.Web.Feed.FeedController do
activities =
%{
"type" => ["Create"],
"whole_db" => true,
"actor_id" => user.ap_id
}
|> Map.merge(Map.take(params, ["max_id"]))
|> put_in_if_exist("max_id", params["max_id"])
|> ActivityPub.fetch_public_activities()
conn
|> put_resp_content_type("application/atom+xml")
|> render("feed.xml",
|> put_view(FeedView)
|> render("user.xml",
user: user,
activities: activities,
feed_config: Pleroma.Config.get([:feed])

View file

@ -124,15 +124,18 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
) do
params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"])
if ScheduledActivity.far_enough?(scheduled_at) do
with {:ok, scheduled_activity} <-
ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do
conn
|> put_view(ScheduledActivityView)
|> render("show.json", scheduled_activity: scheduled_activity)
end
with {:far_enough, true} <- {:far_enough, ScheduledActivity.far_enough?(scheduled_at)},
attrs <- %{"params" => params, "scheduled_at" => scheduled_at},
{:ok, scheduled_activity} <- ScheduledActivity.create(user, attrs) do
conn
|> put_view(ScheduledActivityView)
|> render("show.json", scheduled_activity: scheduled_activity)
else
create(conn, Map.drop(params, ["scheduled_at"]))
{:far_enough, _} ->
create(conn, Map.drop(params, ["scheduled_at"]))
error ->
error
end
end

View file

@ -7,62 +7,8 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionController do
require Logger
alias Pleroma.Config
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.MediaProxy
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :index)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/suggestions"
def index(%{assigns: %{user: user}} = conn, _) do
if Config.get([:suggestions, :enabled], false) do
with {:ok, data} <- fetch_suggestions(user) do
limit = Config.get([:suggestions, :limit], 23)
data =
data
|> Enum.slice(0, limit)
|> Enum.map(fn x ->
x
|> Map.put("id", fetch_suggestion_id(x))
|> Map.put("avatar", MediaProxy.url(x["avatar"]))
|> Map.put("avatar_static", MediaProxy.url(x["avatar_static"]))
end)
json(conn, data)
end
else
json(conn, [])
end
end
defp fetch_suggestions(user) do
api = Config.get([:suggestions, :third_party_engine], "")
timeout = Config.get([:suggestions, :timeout], 5000)
host = Config.get([Pleroma.Web.Endpoint, :url, :host])
url =
api
|> String.replace("{{host}}", host)
|> String.replace("{{user}}", user.nickname)
with {:ok, %{status: 200, body: body}} <-
Pleroma.HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]) do
Jason.decode(body)
else
e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
end
end
defp fetch_suggestion_id(attrs) do
case User.get_or_fetch(attrs["acct"]) do
{:ok, %User{id: id}} -> id
_ -> 0
end
def index(conn, _) do
json(conn, [])
end
end

View file

@ -67,7 +67,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
end
defp do_render("show.json", %{user: user} = opts) do
display_name = HTML.strip_tags(user.name || user.nickname)
display_name = user.name || user.nickname
image = User.avatar_url(user) |> MediaProxy.url()
header = User.banner_url(user) |> MediaProxy.url()
@ -105,7 +105,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> User.fields()
|> Enum.map(fn %{"name" => name, "value" => value} ->
%{
"name" => Pleroma.HTML.strip_tags(name),
"name" => name,
"value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
}
end)

View file

@ -7,10 +7,6 @@ defmodule Pleroma.Web.MastodonAPI.AppView do
alias Pleroma.Web.OAuth.App
@vapid_key :web_push_encryption
|> Application.get_env(:vapid_details, [])
|> Keyword.get(:public_key)
def render("show.json", %{app: %App{} = app}) do
%{
id: app.id |> to_string,
@ -32,8 +28,10 @@ defmodule Pleroma.Web.MastodonAPI.AppView do
end
defp with_vapid_key(data) do
if @vapid_key do
Map.put(data, "vapid_key", @vapid_key)
vapid_key = Application.get_env(:web_push_encryption, :vapid_details, [])[:public_key]
if vapid_key do
Map.put(data, "vapid_key", vapid_key)
else
data
end

View file

@ -37,18 +37,37 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
}
case mastodon_type do
"mention" -> put_status(response, activity, user)
"favourite" -> put_status(response, parent_activity, user)
"reblog" -> put_status(response, parent_activity, user)
"move" -> put_target(response, activity, user)
"follow" -> response
_ -> nil
"mention" ->
put_status(response, activity, user)
"favourite" ->
put_status(response, parent_activity, user)
"reblog" ->
put_status(response, parent_activity, user)
"move" ->
put_target(response, activity, user)
"follow" ->
response
"pleroma:emoji_reaction" ->
put_status(response, parent_activity, user) |> put_emoji(activity)
_ ->
nil
end
else
_ -> nil
end
end
defp put_emoji(response, activity) do
response
|> Map.put(:emoji, activity.data["content"])
end
defp put_status(response, activity, user) do
Map.put(response, :status, StatusView.render("show.json", %{activity: activity, for: user}))
end

View file

@ -5,7 +5,6 @@
defmodule Pleroma.Web.MastodonAPI.PollView do
use Pleroma.Web, :view
alias Pleroma.HTML
alias Pleroma.Web.CommonAPI.Utils
def render("show.json", %{object: object, multiple: multiple, options: options} = params) do
@ -57,7 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.PollView do
current_count = option["replies"]["totalItems"] || 0
{%{
title: HTML.strip_tags(name),
title: name,
votes_count: current_count
}, current_count + count}
end)

View file

@ -216,21 +216,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
summary = object.data["summary"] || ""
summary_html =
summary
|> HTML.get_cached_scrubbed_html_for_activity(
User.html_filter_policy(opts[:for]),
activity,
"mastoapi:summary"
)
summary_plaintext =
summary
|> HTML.get_cached_stripped_html_for_activity(
activity,
"mastoapi:summary"
)
card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity))
url =
@ -256,7 +241,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
emoji_reactions =
with %{data: %{"reactions" => emoji_reactions}} <- object do
Enum.map(emoji_reactions, fn [emoji, users] ->
[emoji, length(users)]
%{
emoji: emoji,
count: length(users),
reacted: !!(opts[:for] && opts[:for].ap_id in users)
}
end)
else
_ -> []
@ -282,7 +271,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
muted: thread_muted? || User.mutes?(opts[:for], user),
pinned: pinned?(activity, user),
sensitive: sensitive,
spoiler_text: summary_html,
spoiler_text: summary,
visibility: get_visibility(object),
media_attachments: attachments,
poll: render(PollView, "show.json", object: object, for: opts[:for]),
@ -299,7 +288,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
conversation_id: get_context_id(activity),
in_reply_to_account_acct: reply_to_user && reply_to_user.nickname,
content: %{"text/plain" => content_plaintext},
spoiler_text: %{"text/plain" => summary_plaintext},
spoiler_text: %{"text/plain" => summary},
expires_at: expires_at,
direct_conversation_id: direct_conversation_id,
thread_muted: thread_muted?,

View file

@ -16,7 +16,7 @@ defmodule Pleroma.Web.Metadata.Providers.Feed do
[
rel: "alternate",
type: "application/atom+xml",
href: Helpers.feed_path(Endpoint, :feed, user.nickname) <> ".atom"
href: Helpers.user_feed_path(Endpoint, :feed, user.nickname) <> ".atom"
], []}
]
end

View file

@ -21,15 +21,22 @@ defmodule Pleroma.Web.Metadata.Utils do
def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) do
content
# html content comes from DB already encoded, decode first and scrub after
|> HtmlEntities.decode()
|> String.replace(~r/<br\s?\/?>/, " ")
|> HTML.strip_tags()
|> scrub_html
|> Emoji.Formatter.demojify()
|> HtmlEntities.decode()
|> Formatter.truncate(max_length)
end
def scrub_html(content) when is_binary(content) do
content
# html content comes from DB already encoded, decode first and scrub after
|> HtmlEntities.decode()
|> String.replace(~r/<br\s?\/?>/, " ")
|> HTML.strip_tags()
end
def scrub_html(content), do: content
def attachment_url(url) do
MediaProxy.url(url)
end

View file

@ -69,9 +69,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
if Config.get([:chat, :enabled]) do
"chat"
end,
if Config.get([:suggestions, :enabled]) do
"suggestions"
end,
if Config.get([:instance, :allow_relay]) do
"relay"
end,
@ -104,11 +101,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
nodeDescription: Config.get([:instance, :description]),
private: !Config.get([:instance, :public], true),
suggestions: %{
enabled: Config.get([:suggestions, :enabled], false),
thirdPartyEngine: Config.get([:suggestions, :third_party_engine], ""),
timeout: Config.get([:suggestions, :timeout], 5000),
limit: Config.get([:suggestions, :limit], 23),
web: Config.get([:suggestions, :web], "")
enabled: false
},
staffAccounts: staff_accounts,
federation: federation_response,

View file

@ -167,17 +167,37 @@ defmodule Pleroma.Web.OAuth.OAuthController do
defp handle_create_authorization_error(
%Plug.Conn{} = conn,
{:auth_active, false},
{:account_status, :confirmation_pending},
%{"authorization" => _} = params
) do
# Per https://github.com/tootsuite/mastodon/blob/
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
conn
|> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address"))
|> put_status(:forbidden)
|> authorize(params)
end
defp handle_create_authorization_error(
%Plug.Conn{} = conn,
{:account_status, :password_reset_pending},
%{"authorization" => _} = params
) do
conn
|> put_flash(:error, dgettext("errors", "Password reset is required"))
|> put_status(:forbidden)
|> authorize(params)
end
defp handle_create_authorization_error(
%Plug.Conn{} = conn,
{:account_status, :deactivated},
%{"authorization" => _} = params
) do
conn
|> put_flash(:error, dgettext("errors", "Your account is currently disabled"))
|> put_status(:forbidden)
|> authorize(params)
end
defp handle_create_authorization_error(%Plug.Conn{} = conn, error, %{"authorization" => _}) do
Authenticator.handle_error(conn, error)
end
@ -218,46 +238,14 @@ defmodule Pleroma.Web.OAuth.OAuthController do
) do
with {:ok, %User{} = user} <- Authenticator.get_user(conn),
{:ok, app} <- Token.Utils.fetch_app(conn),
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
{:user_active, true} <- {:user_active, !user.deactivated},
{:password_reset_pending, false} <-
{:password_reset_pending, user.password_reset_pending},
{:account_status, :active} <- {:account_status, User.account_status(user)},
{:ok, scopes} <- validate_scopes(app, params),
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
{:ok, token} <- Token.exchange_token(app, auth) do
json(conn, Token.Response.build(user, token))
else
{:auth_active, false} ->
# Per https://github.com/tootsuite/mastodon/blob/
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
render_error(
conn,
:forbidden,
"Your login is missing a confirmed e-mail address",
%{},
"missing_confirmed_email"
)
{:user_active, false} ->
render_error(
conn,
:forbidden,
"Your account is currently disabled",
%{},
"account_is_disabled"
)
{:password_reset_pending, true} ->
render_error(
conn,
:forbidden,
"Password reset is required",
%{},
"password_reset_required"
)
_error ->
render_invalid_credentials_error(conn)
error ->
handle_token_exchange_error(conn, error)
end
end
@ -286,6 +274,43 @@ defmodule Pleroma.Web.OAuth.OAuthController do
# Bad request
def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :deactivated}) do
render_error(
conn,
:forbidden,
"Your account is currently disabled",
%{},
"account_is_disabled"
)
end
defp handle_token_exchange_error(
%Plug.Conn{} = conn,
{:account_status, :password_reset_pending}
) do
render_error(
conn,
:forbidden,
"Password reset is required",
%{},
"password_reset_required"
)
end
defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :confirmation_pending}) do
render_error(
conn,
:forbidden,
"Your login is missing a confirmed e-mail address",
%{},
"missing_confirmed_email"
)
end
defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do
render_invalid_credentials_error(conn)
end
def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do
with {:ok, app} <- Token.Utils.fetch_app(conn),
{:ok, _token} <- RevokeToken.revoke(app, params) do
@ -472,7 +497,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
%App{} = app <- Repo.get_by(App, client_id: client_id),
true <- redirect_uri in String.split(app.redirect_uris),
{:ok, scopes} <- validate_scopes(app, auth_attrs),
{:auth_active, true} <- {:auth_active, User.auth_active?(user)} do
{:account_status, :active} <- {:account_status, User.account_status(user)} do
Authorization.create_authorization(app, user, scopes)
end
end

View file

@ -1,34 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.Token.CleanWorker do
@moduledoc """
The module represents functions to clean an expired oauth tokens.
"""
use GenServer
@ten_seconds 10_000
@one_day 86_400_000
alias Pleroma.Web.OAuth.Token
alias Pleroma.Workers.BackgroundWorker
def start_link(_), do: GenServer.start_link(__MODULE__, %{})
def init(_) do
Process.send_after(self(), :perform, @ten_seconds)
{:ok, nil}
end
@doc false
def handle_info(:perform, state) do
BackgroundWorker.enqueue("clean_expired_tokens", %{})
interval = Pleroma.Config.get([:oauth2, :clean_expired_tokens_interval], @one_day)
Process.send_after(self(), :perform, interval)
{:noreply, state}
end
def perform(:clean), do: Token.delete_expired_tokens()
end

View file

@ -573,11 +573,14 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
assumed to be emojis and stored in the new `pack.json` file.
"""
def import_from_fs(conn, _params) do
with {:ok, results} <- File.ls(emoji_dir_path()) do
emoji_path = emoji_dir_path()
with {:ok, %{access: :read_write}} <- File.stat(emoji_path),
{:ok, results} <- File.ls(emoji_path) do
imported_pack_names =
results
|> Enum.filter(fn file ->
dir_path = Path.join(emoji_dir_path(), file)
dir_path = Path.join(emoji_path, file)
# Find the directories that do NOT have pack.json
File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json"))
end)
@ -585,6 +588,11 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
json(conn, imported_pack_names)
else
{:ok, %{access: _}} ->
conn
|> put_status(:internal_server_error)
|> json(%{error: "Error: emoji pack directory must be writable"})
{:error, _} ->
conn
|> put_status(:internal_server_error)

View file

@ -47,9 +47,17 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
Object.normalize(activity) do
reactions =
emoji_reactions
|> Enum.map(fn [emoji, users] ->
users = Enum.map(users, &User.get_cached_by_ap_id/1)
{emoji, AccountView.render("index.json", %{users: users, for: user, as: :user})}
|> Enum.map(fn [emoji, user_ap_ids] ->
users =
Enum.map(user_ap_ids, &User.get_cached_by_ap_id/1)
|> Enum.filter(& &1)
%{
emoji: emoji,
count: length(users),
accounts: AccountView.render("index.json", %{users: users, for: user, as: :user}),
reacted: !!(user && user.ap_id in user_ap_ids)
}
end)
conn

View file

@ -48,6 +48,6 @@ defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do
defp maybe_put_title(meta, _), do: meta
defp get_page_title(html) do
Floki.find(html, "title") |> Floki.text()
Floki.find(html, "html head title") |> List.first() |> Floki.text()
end
end

View file

@ -196,7 +196,7 @@ defmodule Pleroma.Web.Router do
get("/config", AdminAPIController, :config_show)
post("/config", AdminAPIController, :config_update)
get("/config/descriptions", AdminAPIController, :config_descriptions)
get("/config/migrate_from_db", AdminAPIController, :migrate_from_db)
get("/restart", AdminAPIController, :restart)
get("/moderation_log", AdminAPIController, :list_log)
@ -527,8 +527,10 @@ defmodule Pleroma.Web.Router do
get("/notice/:id", OStatus.OStatusController, :notice)
get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
get("/users/:nickname/feed", Feed.FeedController, :feed)
get("/users/:nickname", Feed.FeedController, :feed_redirect)
get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
get("/tags/:tag", Feed.TagController, :feed, as: :tag_feed)
end
scope "/", Pleroma.Web do

View file

@ -138,7 +138,8 @@ defmodule Pleroma.Web.Streamer.Worker do
with parent <- Object.normalize(item) || item,
true <-
Enum.all?([blocked_ap_ids, muted_ap_ids, reblog_muted_ap_ids], &(item.actor not in &1)),
Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids,
true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)),
true <- MapSet.disjoint?(recipients, recipient_blocks),
%{host: item_host} <- URI.parse(item.actor),

View file

@ -9,7 +9,7 @@
<ostatus:conversation ref="<%= activity_context(@activity) %>">
<%= activity_context(@activity) %>
</ostatus:conversation>
<link ref="<%= activity_context(@activity) %>" rel="ostatus:conversation"/>
<link href="<%= activity_context(@activity) %>" rel="ostatus:conversation"/>
<%= if @data["summary"] do %>
<summary><%= @data["summary"] %></summary>

View file

@ -0,0 +1,51 @@
<entry>
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
<%= render @view_module, "_tag_author.atom", assigns %>
<id><%= @data["id"] %></id>
<title><%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %></title>
<content type="html"><%= activity_content(@object) %></content>
<%= if @activity.local do %>
<link type="application/atom+xml" href='<%= @data["id"] %>' rel="self"/>
<link type="text/html" href='<%= @data["id"] %>' rel="alternate"/>
<% else %>
<link type="text/html" href='<%= @data["external_url"] %>' rel="alternate"/>
<% end %>
<published><%= @data["published"] %></published>
<updated><%= @data["published"] %></updated>
<ostatus:conversation ref="<%= activity_context(@activity) %>">
<%= activity_context(@activity) %>
</ostatus:conversation>
<link href="<%= activity_context(@activity) %>" rel="ostatus:conversation"/>
<%= if @data["summary"] do %>
<summary><%= @data["summary"] %></summary>
<% end %>
<%= for id <- @activity.recipients do %>
<%= if id == Pleroma.Constants.as_public() do %>
<link rel="mentioned"
ostatus:object-type="http://activitystrea.ms/schema/1.0/collection"
href="http://activityschema.org/collection/public"/>
<% else %>
<%= unless Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) do %>
<link rel="mentioned"
ostatus:object-type="http://activitystrea.ms/schema/1.0/person"
href="<%= id %>" />
<% end %>
<% end %>
<% end %>
<%= for tag <- @data["tag"] || [] do %>
<category term="<%= tag %>"></category>
<% end %>
<%= for {emoji, file} <- @data["emoji"] || %{} do %>
<link name="<%= emoji %>" rel="emoji" href="<%= file %>"/>
<% end %>
</entry>

View file

@ -0,0 +1,15 @@
<item>
<title><%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %></title>
<guid isPermalink="true"><%= activity_context(@activity) %></guid>
<link><%= activity_context(@activity) %></link>
<pubDate><%= pub_date(@data["published"]) %></pubDate>
<description><%= activity_content(@object) %></description>
<%= for attachment <- @data["attachment"] || [] do %>
<enclosure url="<%= attachment_href(attachment) %>" type="<%= attachment_type(attachment) %>"/>
<% end %>
</item>

View file

@ -0,0 +1,18 @@
<author>
<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
<id><%= @actor.ap_id %></id>
<uri><%= @actor.ap_id %></uri>
<name><%= @actor.nickname %></name>
<summary><%= escape(@actor.bio) %></summary>
<link rel="avatar" href="<%= User.avatar_url(@actor) %>"/>
<%= if User.banner_url(@actor) do %>
<link rel="header" href="<%= User.banner_url(@actor) %>"/>
<% end %>
<%= if @actor.local do %>
<ap_enabled>true</ap_enabled>
<% end %>
<poco:preferredUsername><%= @actor.nickname %></poco:preferredUsername>
<poco:displayName><%= @actor.name %></poco:displayName>
<poco:note><%= escape(@actor.bio) %></poco:note>
</author>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom"
xmlns:thr="http://purl.org/syndication/thread/1.0"
xmlns:georss="http://www.georss.org/georss"
xmlns:activity="http://activitystrea.ms/spec/1.0/"
xmlns:media="http://purl.org/syndication/atommedia"
xmlns:poco="http://portablecontacts.net/spec/1.0"
xmlns:ostatus="http://ostatus.org/schema/1.0"
xmlns:statusnet="http://status.net/schema/api/1/">
<id><%= '#{tag_feed_url(@conn, :feed, @tag)}.rss' %></id>
<title>#<%= @tag %></title>
<subtitle>These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse.</subtitle>
<logo><%= feed_logo() %></logo>
<updated><%= most_recent_update(@activities) %></updated>
<link rel="self" href="<%= '#{tag_feed_url(@conn, :feed, @tag)}.atom' %>" type="application/atom+xml"/>
<%= for activity <- @activities do %>
<%= render @view_module, "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %>
<% end %>
</feed>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:webfeeds="http://webfeeds.org/rss/1.0">
<channel>
<title>#<%= @tag %></title>
<description>These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse.</description>
<link><%= '#{tag_feed_url(@conn, :feed, @tag)}.rss' %></link>
<webfeeds:logo><%= feed_logo() %></webfeeds:logo>
<webfeeds:accentColor>2b90d9</webfeeds:accentColor>
<%= for activity <- @activities do %>
<%= render @view_module, "_tag_activity.xml", Map.merge(assigns, prepare_activity(activity)) %>
<% end %>
</channel>
</rss>

View file

@ -6,16 +6,16 @@
xmlns:poco="http://portablecontacts.net/spec/1.0"
xmlns:ostatus="http://ostatus.org/schema/1.0">
<id><%= feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id>
<id><%= user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id>
<title><%= @user.nickname <> "'s timeline" %></title>
<updated><%= most_recent_update(@activities, @user) %></updated>
<logo><%= logo(@user) %></logo>
<link rel="self" href="<%= '#{feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/>
<link rel="self" href="<%= '#{user_feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/>
<%= render @view_module, "_author.xml", assigns %>
<%= if last_activity(@activities) do %>
<link rel="next" href="<%= '#{feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/>
<link rel="next" href="<%= '#{user_feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/>
<% end %>
<%= for activity <- @activities do %>

View file

@ -1,18 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.ActivityExpirationWorker do
use Pleroma.Workers.WorkerHelper, queue: "activity_expiration"
@impl Oban.Worker
def perform(
%{
"op" => "activity_expiration",
"activity_expiration_id" => activity_expiration_id
},
_job
) do
Pleroma.Daemons.ActivityExpirationDaemon.perform(:execute, activity_expiration_id)
end
end

View file

@ -12,7 +12,10 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
@impl Oban.Worker
def perform(
%{"object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}}},
%{
"op" => "cleanup_attachments",
"object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}}
},
_job
) do
hrefs =
@ -37,7 +40,7 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
)
# The query above can be time consumptive on large instances until we
# refactor how uploads are stored
|> Repo.all(timout: :infinity)
|> Repo.all(timeout: :infinity)
# we should delete 1 object for any given attachment, but don't delete
# files if there are more than 1 object for it
|> Enum.reduce(%{}, fn %{
@ -70,7 +73,11 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
_ -> ""
end
base_url = Pleroma.Config.get([__MODULE__, :base_url], Pleroma.Web.base_url())
base_url =
String.trim_trailing(
Pleroma.Config.get([Pleroma.Upload, :base_url], Pleroma.Web.base_url()),
"/"
)
file_path = String.trim_leading(href, "#{base_url}/#{prefix}")
@ -84,5 +91,5 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
|> Repo.delete_all()
end
def perform(%{"object" => _object}, _job), do: :ok
def perform(%{"op" => "cleanup_attachments", "object" => _object}, _job), do: :ok
end

View file

@ -6,7 +6,6 @@ defmodule Pleroma.Workers.BackgroundWorker do
alias Pleroma.Activity
alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy
alias Pleroma.Web.OAuth.Token.CleanWorker
use Pleroma.Workers.WorkerHelper, queue: "background"
@ -55,10 +54,6 @@ defmodule Pleroma.Workers.BackgroundWorker do
User.perform(:follow_import, follower, followed_identifiers)
end
def perform(%{"op" => "clean_expired_tokens"}, _job) do
CleanWorker.perform(:clean)
end
def perform(%{"op" => "media_proxy_preload", "message" => message}, _job) do
MediaProxyWarmingPolicy.perform(:preload, message)
end

View file

@ -0,0 +1,21 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.Cron.ClearOauthTokenWorker do
@moduledoc """
The worker to cleanup expired oAuth tokens.
"""
use Oban.Worker, queue: "background"
alias Pleroma.Config
alias Pleroma.Web.OAuth.Token
@impl Oban.Worker
def perform(_opts, _job) do
if Config.get([:oauth2, :clean_expired_tokens], false) do
Token.delete_expired_tokens()
end
end
end

View file

@ -0,0 +1,58 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.Cron.DigestEmailsWorker do
@moduledoc """
The worker to send digest emails.
"""
use Oban.Worker, queue: "digest_emails"
alias Pleroma.Config
alias Pleroma.Emails
alias Pleroma.Repo
alias Pleroma.User
import Ecto.Query
require Logger
@impl Oban.Worker
def perform(_opts, _job) do
config = Config.get([:email_notifications, :digest])
if config[:active] do
negative_interval = -Map.fetch!(config, :interval)
inactivity_threshold = Map.fetch!(config, :inactivity_threshold)
inactive_users_query = User.list_inactive_users_query(inactivity_threshold)
now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
from(u in inactive_users_query,
where: fragment(~s(? ->'digest' @> 'true'), u.email_notifications),
where: u.last_digest_emailed_at < datetime_add(^now, ^negative_interval, "day"),
select: u
)
|> Repo.all()
|> send_emails
end
end
def send_emails(users) do
Enum.each(users, &send_email/1)
end
@doc """
Send digest email to the given user.
Updates `last_digest_emailed_at` field for the user and returns the updated user.
"""
@spec send_email(User.t()) :: User.t()
def send_email(user) do
with %Swoosh.Email{} = email <- Emails.UserEmail.digest_email(user) do
Emails.Mailer.deliver_async(email)
end
User.touch_last_digest_emailed_at(user)
end
end

View file

@ -0,0 +1,46 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker do
@moduledoc """
The worker to purge expired activities.
"""
use Oban.Worker, queue: "activity_expiration"
alias Pleroma.Activity
alias Pleroma.ActivityExpiration
alias Pleroma.Config
alias Pleroma.User
alias Pleroma.Web.CommonAPI
require Logger
@interval :timer.minutes(1)
@impl Oban.Worker
def perform(_opts, _job) do
if Config.get([ActivityExpiration, :enabled]) do
Enum.each(ActivityExpiration.due_expirations(@interval), &delete_activity/1)
end
end
def delete_activity(%ActivityExpiration{activity_id: activity_id}) do
with {:activity, %Activity{} = activity} <-
{:activity, Activity.get_by_id_with_object(activity_id)},
{:user, %User{} = user} <- {:user, User.get_by_ap_id(activity.object.data["actor"])} do
CommonAPI.delete(activity.id, user)
else
{:activity, _} ->
Logger.error(
"#{__MODULE__} Couldn't delete expired activity: not found activity ##{activity_id}"
)
{:user, _} ->
Logger.error(
"#{__MODULE__} Couldn't delete expired activity: not found actorof ##{activity_id}"
)
end
end
end

View file

@ -0,0 +1,16 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.Cron.StatsWorker do
@moduledoc """
The worker to update peers statistics.
"""
use Oban.Worker, queue: "background"
@impl Oban.Worker
def perform(_opts, _job) do
Pleroma.Stats.do_collect()
end
end

View file

@ -1,16 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.DigestEmailsWorker do
alias Pleroma.User
use Pleroma.Workers.WorkerHelper, queue: "digest_emails"
@impl Oban.Worker
def perform(%{"op" => "digest_email", "user_id" => user_id}, _job) do
user_id
|> User.get_cached_by_id()
|> Pleroma.Daemons.DigestEmailDaemon.perform()
end
end

View file

@ -3,10 +3,42 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.ScheduledActivityWorker do
@moduledoc """
The worker to post scheduled activity.
"""
use Pleroma.Workers.WorkerHelper, queue: "scheduled_activities"
alias Pleroma.Config
alias Pleroma.ScheduledActivity
alias Pleroma.User
alias Pleroma.Web.CommonAPI
require Logger
@impl Oban.Worker
def perform(%{"op" => "execute", "activity_id" => activity_id}, _job) do
Pleroma.Daemons.ScheduledActivityDaemon.perform(:execute, activity_id)
def perform(%{"activity_id" => activity_id}, _job) do
if Config.get([ScheduledActivity, :enabled]) do
case Pleroma.Repo.get(ScheduledActivity, activity_id) do
%ScheduledActivity{} = scheduled_activity ->
post_activity(scheduled_activity)
_ ->
Logger.error("#{__MODULE__} Couldn't find scheduled activity: #{activity_id}")
end
end
end
defp post_activity(%ScheduledActivity{user_id: user_id, params: params} = scheduled_activity) do
with {:delete, {:ok, _}} <- {:delete, ScheduledActivity.delete(scheduled_activity)},
{:user, %User{} = user} <- {:user, User.get_cached_by_id(user_id)},
{:post, {:ok, _}} <- {:post, CommonAPI.post(user, params)} do
:ok
else
error ->
Logger.error(
"#{__MODULE__} Couldn't create a status from the scheduled activity: #{inspect(error)}"
)
end
end
end