Merge branch 'develop' into tests/mastodon_api_controller.ex
This commit is contained in:
commit
3d722dc200
66 changed files with 1500 additions and 1687 deletions
|
|
@ -235,7 +235,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
cwd: tmp_pack_dir
|
||||
)
|
||||
|
||||
emoji_map = Pleroma.Emoji.make_shortcode_to_file_map(tmp_pack_dir, exts)
|
||||
emoji_map = Pleroma.Emoji.Loader.make_shortcode_to_file_map(tmp_pack_dir, exts)
|
||||
|
||||
File.write!(files_name, Jason.encode!(emoji_map, pretty: true))
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
defmodule Mix.Tasks.Pleroma.User do
|
||||
use Mix.Task
|
||||
import Ecto.Changeset
|
||||
import Mix.Pleroma
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserInviteToken
|
||||
|
|
@ -228,9 +227,9 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
shell_info("Deactivating #{user.nickname}")
|
||||
User.deactivate(user)
|
||||
|
||||
{:ok, friends} = User.get_friends(user)
|
||||
|
||||
Enum.each(friends, fn friend ->
|
||||
user
|
||||
|> User.get_friends()
|
||||
|> Enum.each(fn friend ->
|
||||
user = User.get_cached_by_id(user.id)
|
||||
|
||||
shell_info("Unsubscribing #{friend.nickname} from #{user.nickname}")
|
||||
|
|
@ -405,7 +404,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
start_pleroma()
|
||||
|
||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||
{:ok, _} = User.delete_user_activities(user)
|
||||
User.delete_user_activities(user)
|
||||
shell_info("User #{nickname} statuses deleted.")
|
||||
else
|
||||
_ ->
|
||||
|
|
@ -443,39 +442,21 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
end
|
||||
|
||||
defp set_moderator(user, value) do
|
||||
info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value})
|
||||
|
||||
user_cng =
|
||||
Ecto.Changeset.change(user)
|
||||
|> put_embed(:info, info_cng)
|
||||
|
||||
{:ok, user} = User.update_and_set_cache(user_cng)
|
||||
{:ok, user} = User.update_info(user, &User.Info.admin_api_update(&1, %{is_moderator: value}))
|
||||
|
||||
shell_info("Moderator status of #{user.nickname}: #{user.info.is_moderator}")
|
||||
user
|
||||
end
|
||||
|
||||
defp set_admin(user, value) do
|
||||
info_cng = User.Info.admin_api_update(user.info, %{is_admin: value})
|
||||
|
||||
user_cng =
|
||||
Ecto.Changeset.change(user)
|
||||
|> put_embed(:info, info_cng)
|
||||
|
||||
{:ok, user} = User.update_and_set_cache(user_cng)
|
||||
{:ok, user} = User.update_info(user, &User.Info.admin_api_update(&1, %{is_admin: value}))
|
||||
|
||||
shell_info("Admin status of #{user.nickname}: #{user.info.is_admin}")
|
||||
user
|
||||
end
|
||||
|
||||
defp set_locked(user, value) do
|
||||
info_cng = User.Info.user_upgrade(user.info, %{locked: value})
|
||||
|
||||
user_cng =
|
||||
Ecto.Changeset.change(user)
|
||||
|> put_embed(:info, info_cng)
|
||||
|
||||
{:ok, user} = User.update_and_set_cache(user_cng)
|
||||
{:ok, user} = User.update_info(user, &User.Info.user_upgrade(&1, %{locked: value}))
|
||||
|
||||
shell_info("Locked status of #{user.nickname}: #{user.info.locked}")
|
||||
user
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ defmodule Pleroma.Activity do
|
|||
@type t :: %__MODULE__{}
|
||||
@type actor :: String.t()
|
||||
|
||||
@primary_key {:id, Pleroma.FlakeId, autogenerate: true}
|
||||
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
|
||||
|
||||
# https://github.com/tootsuite/mastodon/blob/master/app/models/notification.rb#L19
|
||||
@mastodon_notification_types %{
|
||||
|
|
@ -139,7 +139,7 @@ defmodule Pleroma.Activity do
|
|||
|
||||
@spec get_by_id(String.t()) :: Activity.t() | nil
|
||||
def get_by_id(id) do
|
||||
case Pleroma.FlakeId.is_flake_id?(id) do
|
||||
case FlakeId.flake_id?(id) do
|
||||
true ->
|
||||
Activity
|
||||
|> where([a], a.id == ^id)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ defmodule Pleroma.ActivityExpiration do
|
|||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.ActivityExpiration
|
||||
alias Pleroma.FlakeId
|
||||
alias Pleroma.Repo
|
||||
|
||||
import Ecto.Changeset
|
||||
|
|
@ -17,7 +16,7 @@ defmodule Pleroma.ActivityExpiration do
|
|||
@min_activity_lifetime :timer.hours(1)
|
||||
|
||||
schema "activity_expirations" do
|
||||
belongs_to(:activity, Activity, type: FlakeId)
|
||||
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
|
||||
field(:scheduled_at, :naive_datetime)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ defmodule Pleroma.Application do
|
|||
Pleroma.Config.TransferTask,
|
||||
Pleroma.Emoji,
|
||||
Pleroma.Captcha,
|
||||
Pleroma.FlakeId,
|
||||
Pleroma.Daemons.ScheduledActivityDaemon,
|
||||
Pleroma.Daemons.ActivityExpirationDaemon
|
||||
] ++
|
||||
|
|
|
|||
|
|
@ -10,20 +10,20 @@ defmodule Pleroma.Bookmark do
|
|||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Bookmark
|
||||
alias Pleroma.FlakeId
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
schema "bookmarks" do
|
||||
belongs_to(:user, User, type: FlakeId)
|
||||
belongs_to(:activity, Activity, type: FlakeId)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@spec create(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()}
|
||||
@spec create(FlakeId.Ecto.CompatType.t(), FlakeId.Ecto.CompatType.t()) ::
|
||||
{:ok, Bookmark.t()} | {:error, Changeset.t()}
|
||||
def create(user_id, activity_id) do
|
||||
attrs = %{
|
||||
user_id: user_id,
|
||||
|
|
@ -37,7 +37,7 @@ defmodule Pleroma.Bookmark do
|
|||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@spec for_user_query(FlakeId.t()) :: Ecto.Query.t()
|
||||
@spec for_user_query(FlakeId.Ecto.CompatType.t()) :: Ecto.Query.t()
|
||||
def for_user_query(user_id) do
|
||||
Bookmark
|
||||
|> where(user_id: ^user_id)
|
||||
|
|
@ -52,7 +52,8 @@ defmodule Pleroma.Bookmark do
|
|||
|> Repo.one()
|
||||
end
|
||||
|
||||
@spec destroy(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()}
|
||||
@spec destroy(FlakeId.Ecto.CompatType.t(), FlakeId.Ecto.CompatType.t()) ::
|
||||
{:ok, Bookmark.t()} | {:error, Changeset.t()}
|
||||
def destroy(user_id, activity_id) do
|
||||
from(b in Bookmark,
|
||||
where: b.user_id == ^user_id,
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ defmodule Pleroma.Conversation.Participation do
|
|||
import Ecto.Query
|
||||
|
||||
schema "conversation_participations" do
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
belongs_to(:conversation, Conversation)
|
||||
field(:read, :boolean, default: false)
|
||||
field(:last_activity_id, Pleroma.FlakeId, virtual: true)
|
||||
field(:last_activity_id, FlakeId.Ecto.CompatType, virtual: true)
|
||||
|
||||
has_many(:recipient_ships, RecipientShip)
|
||||
has_many(:recipients, through: [:recipient_ships, :user])
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ defmodule Pleroma.Conversation.Participation.RecipientShip do
|
|||
import Ecto.Changeset
|
||||
|
||||
schema "conversation_participation_recipient_ships" do
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
belongs_to(:participation, Participation)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.Delivery do
|
|||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.Delivery
|
||||
alias Pleroma.FlakeId
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
|
@ -16,7 +15,7 @@ defmodule Pleroma.Delivery do
|
|||
import Ecto.Query
|
||||
|
||||
schema "deliveries" do
|
||||
belongs_to(:user, User, type: FlakeId)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
belongs_to(:object, Object)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -4,24 +4,37 @@
|
|||
|
||||
defmodule Pleroma.Emoji do
|
||||
@moduledoc """
|
||||
The emojis are loaded from:
|
||||
|
||||
* emoji packs in INSTANCE-DIR/emoji
|
||||
* the files: `config/emoji.txt` and `config/custom_emoji.txt`
|
||||
* glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder
|
||||
|
||||
This GenServer stores in an ETS table the list of the loaded emojis, and also allows to reload the list at runtime.
|
||||
This GenServer stores in an ETS table the list of the loaded emojis,
|
||||
and also allows to reload the list at runtime.
|
||||
"""
|
||||
use GenServer
|
||||
|
||||
alias Pleroma.Emoji.Loader
|
||||
|
||||
require Logger
|
||||
|
||||
@type pattern :: Regex.t() | module() | String.t()
|
||||
@type patterns :: pattern() | [pattern()]
|
||||
@type group_patterns :: keyword(patterns())
|
||||
|
||||
@ets __MODULE__.Ets
|
||||
@ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
|
||||
@ets_options [
|
||||
:ordered_set,
|
||||
:protected,
|
||||
:named_table,
|
||||
{:read_concurrency, true}
|
||||
]
|
||||
|
||||
defstruct [:code, :file, :tags, :safe_code, :safe_file]
|
||||
|
||||
@doc "Build emoji struct"
|
||||
def build({code, file, tags}) do
|
||||
%__MODULE__{
|
||||
code: code,
|
||||
file: file,
|
||||
tags: tags,
|
||||
safe_code: Pleroma.HTML.strip_tags(code),
|
||||
safe_file: Pleroma.HTML.strip_tags(file)
|
||||
}
|
||||
end
|
||||
|
||||
def build({code, file}), do: build({code, file, []})
|
||||
|
||||
@doc false
|
||||
def start_link(_) do
|
||||
|
|
@ -44,11 +57,14 @@ defmodule Pleroma.Emoji do
|
|||
end
|
||||
|
||||
@doc "Returns all the emojos!!"
|
||||
@spec get_all() :: [{String.t(), String.t()}, ...]
|
||||
@spec get_all() :: list({String.t(), String.t(), String.t()})
|
||||
def get_all do
|
||||
:ets.tab2list(@ets)
|
||||
end
|
||||
|
||||
@doc "Clear out old emojis"
|
||||
def clear_all, do: :ets.delete_all_objects(@ets)
|
||||
|
||||
@doc false
|
||||
def init(_) do
|
||||
@ets = :ets.new(@ets, @ets_options)
|
||||
|
|
@ -58,13 +74,13 @@ defmodule Pleroma.Emoji do
|
|||
|
||||
@doc false
|
||||
def handle_cast(:reload, state) do
|
||||
load()
|
||||
update_emojis(Loader.load())
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(:reload, _from, state) do
|
||||
load()
|
||||
update_emojis(Loader.load())
|
||||
{:reply, :ok, state}
|
||||
end
|
||||
|
||||
|
|
@ -75,207 +91,11 @@ defmodule Pleroma.Emoji do
|
|||
|
||||
@doc false
|
||||
def code_change(_old_vsn, state, _extra) do
|
||||
load()
|
||||
update_emojis(Loader.load())
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defp load do
|
||||
emoji_dir_path =
|
||||
Path.join(
|
||||
Pleroma.Config.get!([:instance, :static_dir]),
|
||||
"emoji"
|
||||
)
|
||||
|
||||
emoji_groups = Pleroma.Config.get([:emoji, :groups])
|
||||
|
||||
case File.ls(emoji_dir_path) do
|
||||
{:error, :enoent} ->
|
||||
# The custom emoji directory doesn't exist,
|
||||
# don't do anything
|
||||
nil
|
||||
|
||||
{:error, e} ->
|
||||
# There was some other error
|
||||
Logger.error("Could not access the custom emoji directory #{emoji_dir_path}: #{e}")
|
||||
|
||||
{:ok, results} ->
|
||||
grouped =
|
||||
Enum.group_by(results, fn file -> File.dir?(Path.join(emoji_dir_path, file)) end)
|
||||
|
||||
packs = grouped[true] || []
|
||||
files = grouped[false] || []
|
||||
|
||||
# Print the packs we've found
|
||||
Logger.info("Found emoji packs: #{Enum.join(packs, ", ")}")
|
||||
|
||||
if not Enum.empty?(files) do
|
||||
Logger.warn(
|
||||
"Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{
|
||||
Enum.join(files, ", ")
|
||||
}"
|
||||
)
|
||||
end
|
||||
|
||||
emojis =
|
||||
Enum.flat_map(
|
||||
packs,
|
||||
fn pack -> load_pack(Path.join(emoji_dir_path, pack), emoji_groups) end
|
||||
)
|
||||
|
||||
# Clear out old emojis
|
||||
:ets.delete_all_objects(@ets)
|
||||
|
||||
true = :ets.insert(@ets, emojis)
|
||||
end
|
||||
|
||||
# Compat thing for old custom emoji handling & default emoji,
|
||||
# it should run even if there are no emoji packs
|
||||
shortcode_globs = Pleroma.Config.get([:emoji, :shortcode_globs], [])
|
||||
|
||||
emojis =
|
||||
(load_from_file("config/emoji.txt", emoji_groups) ++
|
||||
load_from_file("config/custom_emoji.txt", emoji_groups) ++
|
||||
load_from_globs(shortcode_globs, emoji_groups))
|
||||
|> Enum.reject(fn value -> value == nil end)
|
||||
|
||||
true = :ets.insert(@ets, emojis)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp load_pack(pack_dir, emoji_groups) do
|
||||
pack_name = Path.basename(pack_dir)
|
||||
|
||||
pack_file = Path.join(pack_dir, "pack.json")
|
||||
|
||||
if File.exists?(pack_file) do
|
||||
contents = Jason.decode!(File.read!(pack_file))
|
||||
|
||||
contents["files"]
|
||||
|> Enum.map(fn {name, rel_file} ->
|
||||
filename = Path.join("/emoji/#{pack_name}", rel_file)
|
||||
{name, filename, pack_name}
|
||||
end)
|
||||
else
|
||||
# Load from emoji.txt / all files
|
||||
emoji_txt = Path.join(pack_dir, "emoji.txt")
|
||||
|
||||
if File.exists?(emoji_txt) do
|
||||
load_from_file(emoji_txt, emoji_groups)
|
||||
else
|
||||
extensions = Pleroma.Config.get([:emoji, :pack_extensions])
|
||||
|
||||
Logger.info(
|
||||
"No emoji.txt found for pack \"#{pack_name}\", assuming all #{
|
||||
Enum.join(extensions, ", ")
|
||||
} files are emoji"
|
||||
)
|
||||
|
||||
make_shortcode_to_file_map(pack_dir, extensions)
|
||||
|> Enum.map(fn {shortcode, rel_file} ->
|
||||
filename = Path.join("/emoji/#{pack_name}", rel_file)
|
||||
|
||||
{shortcode, filename, [to_string(match_extra(emoji_groups, filename))]}
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_shortcode_to_file_map(pack_dir, exts) do
|
||||
find_all_emoji(pack_dir, exts)
|
||||
|> Enum.map(&Path.relative_to(&1, pack_dir))
|
||||
|> Enum.map(fn f -> {f |> Path.basename() |> Path.rootname(), f} end)
|
||||
|> Enum.into(%{})
|
||||
end
|
||||
|
||||
def find_all_emoji(dir, exts) do
|
||||
Enum.reduce(
|
||||
File.ls!(dir),
|
||||
[],
|
||||
fn f, acc ->
|
||||
filepath = Path.join(dir, f)
|
||||
|
||||
if File.dir?(filepath) do
|
||||
acc ++ find_all_emoji(filepath, exts)
|
||||
else
|
||||
acc ++ [filepath]
|
||||
end
|
||||
end
|
||||
)
|
||||
|> Enum.filter(fn f -> Path.extname(f) in exts end)
|
||||
end
|
||||
|
||||
defp load_from_file(file, emoji_groups) do
|
||||
if File.exists?(file) do
|
||||
load_from_file_stream(File.stream!(file), emoji_groups)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp load_from_file_stream(stream, emoji_groups) do
|
||||
stream
|
||||
|> Stream.map(&String.trim/1)
|
||||
|> Stream.map(fn line ->
|
||||
case String.split(line, ~r/,\s*/) do
|
||||
[name, file] ->
|
||||
{name, file, [to_string(match_extra(emoji_groups, file))]}
|
||||
|
||||
[name, file | tags] ->
|
||||
{name, file, tags}
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|> Enum.to_list()
|
||||
end
|
||||
|
||||
defp load_from_globs(globs, emoji_groups) 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 ->
|
||||
tag = match_extra(emoji_groups, Path.join("/", Path.relative_to(path, static_path)))
|
||||
shortcode = Path.basename(path, Path.extname(path))
|
||||
external_path = Path.join("/", Path.relative_to(path, static_path))
|
||||
{shortcode, external_path, [to_string(tag)]}
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds a matching group for the given emoji filename
|
||||
"""
|
||||
@spec match_extra(group_patterns(), String.t()) :: atom() | nil
|
||||
def match_extra(group_patterns, filename) do
|
||||
match_group_patterns(group_patterns, fn pattern ->
|
||||
case pattern do
|
||||
%Regex{} = regex -> Regex.match?(regex, filename)
|
||||
string when is_binary(string) -> filename == string
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp match_group_patterns(group_patterns, matcher) do
|
||||
Enum.find_value(group_patterns, fn {group, patterns} ->
|
||||
patterns =
|
||||
patterns
|
||||
|> List.wrap()
|
||||
|> Enum.map(fn pattern ->
|
||||
if String.contains?(pattern, "*") do
|
||||
~r(#{String.replace(pattern, "*", ".*")})
|
||||
else
|
||||
pattern
|
||||
end
|
||||
end)
|
||||
|
||||
Enum.any?(patterns, matcher) && group
|
||||
end)
|
||||
defp update_emojis(emojis) do
|
||||
:ets.insert(@ets, emojis)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
59
lib/pleroma/emoji/formatter.ex
Normal file
59
lib/pleroma/emoji/formatter.ex
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Emoji.Formatter do
|
||||
alias Pleroma.Emoji
|
||||
alias Pleroma.HTML
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
def emojify(text) do
|
||||
emojify(text, Emoji.get_all())
|
||||
end
|
||||
|
||||
def emojify(text, nil), do: text
|
||||
|
||||
def emojify(text, emoji, strip \\ false) do
|
||||
Enum.reduce(emoji, text, fn
|
||||
{_, %Emoji{safe_code: emoji, safe_file: file}}, text ->
|
||||
String.replace(text, ":#{emoji}:", prepare_emoji_html(emoji, file, strip))
|
||||
|
||||
{unsafe_emoji, unsafe_file}, text ->
|
||||
emoji = HTML.strip_tags(unsafe_emoji)
|
||||
file = HTML.strip_tags(unsafe_file)
|
||||
String.replace(text, ":#{emoji}:", prepare_emoji_html(emoji, file, strip))
|
||||
end)
|
||||
|> HTML.filter_tags()
|
||||
end
|
||||
|
||||
defp prepare_emoji_html(_emoji, _file, true), do: ""
|
||||
|
||||
defp prepare_emoji_html(emoji, file, _strip) do
|
||||
"<img class='emoji' alt='#{emoji}' title='#{emoji}' src='#{MediaProxy.url(file)}' />"
|
||||
end
|
||||
|
||||
def demojify(text) do
|
||||
emojify(text, Emoji.get_all(), true)
|
||||
end
|
||||
|
||||
def demojify(text, nil), do: text
|
||||
|
||||
@doc "Outputs a list of the emoji-shortcodes in a text"
|
||||
def get_emoji(text) when is_binary(text) do
|
||||
Enum.filter(Emoji.get_all(), fn {emoji, %Emoji{}} ->
|
||||
String.contains?(text, ":#{emoji}:")
|
||||
end)
|
||||
end
|
||||
|
||||
def get_emoji(_), do: []
|
||||
|
||||
@doc "Outputs a list of the emoji-Maps in a text"
|
||||
def get_emoji_map(text) when is_binary(text) do
|
||||
get_emoji(text)
|
||||
|> Enum.reduce(%{}, fn {name, %Emoji{file: file}}, acc ->
|
||||
Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
|
||||
end)
|
||||
end
|
||||
|
||||
def get_emoji_map(_), do: []
|
||||
end
|
||||
224
lib/pleroma/emoji/loader.ex
Normal file
224
lib/pleroma/emoji/loader.ex
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Emoji.Loader do
|
||||
@moduledoc """
|
||||
The Loader emoji from:
|
||||
|
||||
* emoji packs in INSTANCE-DIR/emoji
|
||||
* the files: `config/emoji.txt` and `config/custom_emoji.txt`
|
||||
* glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder
|
||||
"""
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Emoji
|
||||
|
||||
require Logger
|
||||
|
||||
@type pattern :: Regex.t() | module() | String.t()
|
||||
@type patterns :: pattern() | [pattern()]
|
||||
@type group_patterns :: keyword(patterns())
|
||||
@type emoji :: {String.t(), Emoji.t()}
|
||||
|
||||
@doc """
|
||||
Loads emojis from files/packs.
|
||||
|
||||
returns list emojis in format:
|
||||
`{"000", "/emoji/freespeechextremist.com/000.png", ["Custom"]}`
|
||||
"""
|
||||
@spec load() :: list(emoji)
|
||||
def load do
|
||||
emoji_dir_path = Path.join(Config.get!([:instance, :static_dir]), "emoji")
|
||||
|
||||
emoji_groups = Config.get([:emoji, :groups])
|
||||
|
||||
emojis =
|
||||
case File.ls(emoji_dir_path) do
|
||||
{:error, :enoent} ->
|
||||
# The custom emoji directory doesn't exist,
|
||||
# don't do anything
|
||||
[]
|
||||
|
||||
{:error, e} ->
|
||||
# There was some other error
|
||||
Logger.error("Could not access the custom emoji directory #{emoji_dir_path}: #{e}")
|
||||
[]
|
||||
|
||||
{:ok, results} ->
|
||||
grouped =
|
||||
Enum.group_by(results, fn file ->
|
||||
File.dir?(Path.join(emoji_dir_path, file))
|
||||
end)
|
||||
|
||||
packs = grouped[true] || []
|
||||
files = grouped[false] || []
|
||||
|
||||
# Print the packs we've found
|
||||
Logger.info("Found emoji packs: #{Enum.join(packs, ", ")}")
|
||||
|
||||
if not Enum.empty?(files) do
|
||||
Logger.warn(
|
||||
"Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{
|
||||
Enum.join(files, ", ")
|
||||
}"
|
||||
)
|
||||
end
|
||||
|
||||
emojis =
|
||||
Enum.flat_map(packs, fn pack ->
|
||||
load_pack(Path.join(emoji_dir_path, pack), emoji_groups)
|
||||
end)
|
||||
|
||||
Emoji.clear_all()
|
||||
emojis
|
||||
end
|
||||
|
||||
# Compat thing for old custom emoji handling & default emoji,
|
||||
# it should run even if there are no emoji packs
|
||||
shortcode_globs = Config.get([:emoji, :shortcode_globs], [])
|
||||
|
||||
emojis_txt =
|
||||
(load_from_file("config/emoji.txt", emoji_groups) ++
|
||||
load_from_file("config/custom_emoji.txt", emoji_groups) ++
|
||||
load_from_globs(shortcode_globs, emoji_groups))
|
||||
|> Enum.reject(fn value -> value == nil end)
|
||||
|
||||
Enum.map(emojis ++ emojis_txt, &prepare_emoji/1)
|
||||
end
|
||||
|
||||
defp prepare_emoji({code, _, _} = emoji), do: {code, Emoji.build(emoji)}
|
||||
|
||||
defp load_pack(pack_dir, emoji_groups) do
|
||||
pack_name = Path.basename(pack_dir)
|
||||
|
||||
pack_file = Path.join(pack_dir, "pack.json")
|
||||
|
||||
if File.exists?(pack_file) do
|
||||
contents = Jason.decode!(File.read!(pack_file))
|
||||
|
||||
contents["files"]
|
||||
|> Enum.map(fn {name, rel_file} ->
|
||||
filename = Path.join("/emoji/#{pack_name}", rel_file)
|
||||
{name, filename, ["pack:#{pack_name}"]}
|
||||
end)
|
||||
else
|
||||
# Load from emoji.txt / all files
|
||||
emoji_txt = Path.join(pack_dir, "emoji.txt")
|
||||
|
||||
if File.exists?(emoji_txt) do
|
||||
load_from_file(emoji_txt, emoji_groups)
|
||||
else
|
||||
extensions = Pleroma.Config.get([:emoji, :pack_extensions])
|
||||
|
||||
Logger.info(
|
||||
"No emoji.txt found for pack \"#{pack_name}\", assuming all #{
|
||||
Enum.join(extensions, ", ")
|
||||
} files are emoji"
|
||||
)
|
||||
|
||||
make_shortcode_to_file_map(pack_dir, extensions)
|
||||
|> Enum.map(fn {shortcode, rel_file} ->
|
||||
filename = Path.join("/emoji/#{pack_name}", rel_file)
|
||||
|
||||
{shortcode, filename, [to_string(match_extra(emoji_groups, filename))]}
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_shortcode_to_file_map(pack_dir, exts) do
|
||||
find_all_emoji(pack_dir, exts)
|
||||
|> Enum.map(&Path.relative_to(&1, pack_dir))
|
||||
|> Enum.map(fn f -> {f |> Path.basename() |> Path.rootname(), f} end)
|
||||
|> Enum.into(%{})
|
||||
end
|
||||
|
||||
def find_all_emoji(dir, exts) do
|
||||
dir
|
||||
|> File.ls!()
|
||||
|> Enum.flat_map(fn f ->
|
||||
filepath = Path.join(dir, f)
|
||||
|
||||
if File.dir?(filepath) do
|
||||
find_all_emoji(filepath, exts)
|
||||
else
|
||||
[filepath]
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(fn f -> Path.extname(f) in exts end)
|
||||
end
|
||||
|
||||
defp load_from_file(file, emoji_groups) do
|
||||
if File.exists?(file) do
|
||||
load_from_file_stream(File.stream!(file), emoji_groups)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp load_from_file_stream(stream, emoji_groups) do
|
||||
stream
|
||||
|> Stream.map(&String.trim/1)
|
||||
|> Stream.map(fn line ->
|
||||
case String.split(line, ~r/,\s*/) do
|
||||
[name, file] ->
|
||||
{name, file, [to_string(match_extra(emoji_groups, file))]}
|
||||
|
||||
[name, file | tags] ->
|
||||
{name, file, tags}
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|> Enum.to_list()
|
||||
end
|
||||
|
||||
defp load_from_globs(globs, emoji_groups) 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 ->
|
||||
tag = match_extra(emoji_groups, Path.join("/", Path.relative_to(path, static_path)))
|
||||
shortcode = Path.basename(path, Path.extname(path))
|
||||
external_path = Path.join("/", Path.relative_to(path, static_path))
|
||||
{shortcode, external_path, [to_string(tag)]}
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds a matching group for the given emoji filename
|
||||
"""
|
||||
@spec match_extra(group_patterns(), String.t()) :: atom() | nil
|
||||
def match_extra(group_patterns, filename) do
|
||||
match_group_patterns(group_patterns, fn pattern ->
|
||||
case pattern do
|
||||
%Regex{} = regex -> Regex.match?(regex, filename)
|
||||
string when is_binary(string) -> filename == string
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp match_group_patterns(group_patterns, matcher) do
|
||||
Enum.find_value(group_patterns, fn {group, patterns} ->
|
||||
patterns =
|
||||
patterns
|
||||
|> List.wrap()
|
||||
|> Enum.map(fn pattern ->
|
||||
if String.contains?(pattern, "*") do
|
||||
~r(#{String.replace(pattern, "*", ".*")})
|
||||
else
|
||||
pattern
|
||||
end
|
||||
end)
|
||||
|
||||
Enum.any?(patterns, matcher) && group
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
@ -12,7 +12,7 @@ defmodule Pleroma.Filter do
|
|||
alias Pleroma.User
|
||||
|
||||
schema "filters" do
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
field(:filter_id, :integer)
|
||||
field(:hide, :boolean, default: false)
|
||||
field(:whole_word, :boolean, default: true)
|
||||
|
|
|
|||
|
|
@ -1,182 +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.FlakeId do
|
||||
@moduledoc """
|
||||
Flake is a decentralized, k-ordered id generation service.
|
||||
|
||||
Adapted from:
|
||||
|
||||
* [flaky](https://github.com/nirvana/flaky), released under the terms of the Truly Free License,
|
||||
* [Flake](https://github.com/boundary/flake), Copyright 2012, Boundary, Apache License, Version 2.0
|
||||
"""
|
||||
|
||||
@type t :: binary
|
||||
|
||||
use Ecto.Type
|
||||
use GenServer
|
||||
require Logger
|
||||
alias __MODULE__
|
||||
import Kernel, except: [to_string: 1]
|
||||
|
||||
defstruct node: nil, time: 0, sq: 0
|
||||
|
||||
@doc "Converts a binary Flake to a String"
|
||||
def to_string(<<0::integer-size(64), id::integer-size(64)>>) do
|
||||
Kernel.to_string(id)
|
||||
end
|
||||
|
||||
def to_string(<<_::integer-size(64), _::integer-size(48), _::integer-size(16)>> = flake) do
|
||||
encode_base62(flake)
|
||||
end
|
||||
|
||||
def to_string(s), do: s
|
||||
|
||||
def from_string(int) when is_integer(int) do
|
||||
from_string(Kernel.to_string(int))
|
||||
end
|
||||
|
||||
for i <- [-1, 0] do
|
||||
def from_string(unquote(i)), do: <<0::integer-size(128)>>
|
||||
def from_string(unquote(Kernel.to_string(i))), do: <<0::integer-size(128)>>
|
||||
end
|
||||
|
||||
def from_string(<<_::integer-size(128)>> = flake), do: flake
|
||||
|
||||
def from_string(string) when is_binary(string) and byte_size(string) < 18 do
|
||||
case Integer.parse(string) do
|
||||
{id, ""} -> <<0::integer-size(64), id::integer-size(64)>>
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def from_string(string) do
|
||||
string |> decode_base62 |> from_integer
|
||||
end
|
||||
|
||||
def to_integer(<<integer::integer-size(128)>>), do: integer
|
||||
|
||||
def from_integer(integer) do
|
||||
<<_time::integer-size(64), _node::integer-size(48), _seq::integer-size(16)>> =
|
||||
<<integer::integer-size(128)>>
|
||||
end
|
||||
|
||||
@doc "Generates a Flake"
|
||||
@spec get :: binary
|
||||
def get, do: to_string(:gen_server.call(:flake, :get))
|
||||
|
||||
# checks that ID is is valid FlakeID
|
||||
#
|
||||
@spec is_flake_id?(String.t()) :: boolean
|
||||
def is_flake_id?(id), do: is_flake_id?(String.to_charlist(id), true)
|
||||
defp is_flake_id?([c | cs], true) when c >= ?0 and c <= ?9, do: is_flake_id?(cs, true)
|
||||
defp is_flake_id?([c | cs], true) when c >= ?A and c <= ?Z, do: is_flake_id?(cs, true)
|
||||
defp is_flake_id?([c | cs], true) when c >= ?a and c <= ?z, do: is_flake_id?(cs, true)
|
||||
defp is_flake_id?([], true), do: true
|
||||
defp is_flake_id?(_, _), do: false
|
||||
|
||||
# -- Ecto.Type API
|
||||
@impl Ecto.Type
|
||||
def type, do: :uuid
|
||||
|
||||
@impl Ecto.Type
|
||||
def cast(value) do
|
||||
{:ok, FlakeId.to_string(value)}
|
||||
end
|
||||
|
||||
@impl Ecto.Type
|
||||
def load(value) do
|
||||
{:ok, FlakeId.to_string(value)}
|
||||
end
|
||||
|
||||
@impl Ecto.Type
|
||||
def dump(value) do
|
||||
{:ok, FlakeId.from_string(value)}
|
||||
end
|
||||
|
||||
def autogenerate, do: get()
|
||||
|
||||
# -- GenServer API
|
||||
def start_link(_) do
|
||||
:gen_server.start_link({:local, :flake}, __MODULE__, [], [])
|
||||
end
|
||||
|
||||
@impl GenServer
|
||||
def init([]) do
|
||||
{:ok, %FlakeId{node: worker_id(), time: time()}}
|
||||
end
|
||||
|
||||
@impl GenServer
|
||||
def handle_call(:get, _from, state) do
|
||||
{flake, new_state} = get(time(), state)
|
||||
{:reply, flake, new_state}
|
||||
end
|
||||
|
||||
# Matches when the calling time is the same as the state time. Incr. sq
|
||||
defp get(time, %FlakeId{time: time, node: node, sq: seq}) do
|
||||
new_state = %FlakeId{time: time, node: node, sq: seq + 1}
|
||||
{gen_flake(new_state), new_state}
|
||||
end
|
||||
|
||||
# Matches when the times are different, reset sq
|
||||
defp get(newtime, %FlakeId{time: time, node: node}) when newtime > time do
|
||||
new_state = %FlakeId{time: newtime, node: node, sq: 0}
|
||||
{gen_flake(new_state), new_state}
|
||||
end
|
||||
|
||||
# Error when clock is running backwards
|
||||
defp get(newtime, %FlakeId{time: time}) when newtime < time do
|
||||
{:error, :clock_running_backwards}
|
||||
end
|
||||
|
||||
defp gen_flake(%FlakeId{time: time, node: node, sq: seq}) do
|
||||
<<time::integer-size(64), node::integer-size(48), seq::integer-size(16)>>
|
||||
end
|
||||
|
||||
defp nthchar_base62(n) when n <= 9, do: ?0 + n
|
||||
defp nthchar_base62(n) when n <= 35, do: ?A + n - 10
|
||||
defp nthchar_base62(n), do: ?a + n - 36
|
||||
|
||||
defp encode_base62(<<integer::integer-size(128)>>) do
|
||||
integer
|
||||
|> encode_base62([])
|
||||
|> List.to_string()
|
||||
end
|
||||
|
||||
defp encode_base62(int, acc) when int < 0, do: encode_base62(-int, acc)
|
||||
defp encode_base62(int, []) when int == 0, do: '0'
|
||||
defp encode_base62(int, acc) when int == 0, do: acc
|
||||
|
||||
defp encode_base62(int, acc) do
|
||||
r = rem(int, 62)
|
||||
id = div(int, 62)
|
||||
acc = [nthchar_base62(r) | acc]
|
||||
encode_base62(id, acc)
|
||||
end
|
||||
|
||||
defp decode_base62(s) do
|
||||
decode_base62(String.to_charlist(s), 0)
|
||||
end
|
||||
|
||||
defp decode_base62([c | cs], acc) when c >= ?0 and c <= ?9,
|
||||
do: decode_base62(cs, 62 * acc + (c - ?0))
|
||||
|
||||
defp decode_base62([c | cs], acc) when c >= ?A and c <= ?Z,
|
||||
do: decode_base62(cs, 62 * acc + (c - ?A + 10))
|
||||
|
||||
defp decode_base62([c | cs], acc) when c >= ?a and c <= ?z,
|
||||
do: decode_base62(cs, 62 * acc + (c - ?a + 36))
|
||||
|
||||
defp decode_base62([], acc), do: acc
|
||||
|
||||
defp time do
|
||||
{mega_seconds, seconds, micro_seconds} = :erlang.timestamp()
|
||||
1_000_000_000 * mega_seconds + seconds * 1000 + :erlang.trunc(micro_seconds / 1000)
|
||||
end
|
||||
|
||||
defp worker_id do
|
||||
<<worker::integer-size(48)>> = :crypto.strong_rand_bytes(6)
|
||||
worker
|
||||
end
|
||||
end
|
||||
|
|
@ -3,10 +3,8 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Formatter do
|
||||
alias Pleroma.Emoji
|
||||
alias Pleroma.HTML
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
@safe_mention_regex ~r/^(\s*(?<mentions>(@.+?\s+){1,})+)(?<rest>.*)/s
|
||||
@link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui
|
||||
|
|
@ -100,51 +98,6 @@ defmodule Pleroma.Formatter do
|
|||
end
|
||||
end
|
||||
|
||||
def emojify(text) do
|
||||
emojify(text, Emoji.get_all())
|
||||
end
|
||||
|
||||
def emojify(text, nil), do: text
|
||||
|
||||
def emojify(text, emoji, strip \\ false) do
|
||||
Enum.reduce(emoji, text, fn emoji_data, text ->
|
||||
emoji = HTML.strip_tags(elem(emoji_data, 0))
|
||||
file = HTML.strip_tags(elem(emoji_data, 1))
|
||||
|
||||
html =
|
||||
if not strip do
|
||||
"<img class='emoji' alt='#{emoji}' title='#{emoji}' src='#{MediaProxy.url(file)}' />"
|
||||
else
|
||||
""
|
||||
end
|
||||
|
||||
String.replace(text, ":#{emoji}:", html) |> HTML.filter_tags()
|
||||
end)
|
||||
end
|
||||
|
||||
def demojify(text) do
|
||||
emojify(text, Emoji.get_all(), true)
|
||||
end
|
||||
|
||||
def demojify(text, nil), do: text
|
||||
|
||||
@doc "Outputs a list of the emoji-shortcodes in a text"
|
||||
def get_emoji(text) when is_binary(text) do
|
||||
Enum.filter(Emoji.get_all(), fn {emoji, _, _} -> String.contains?(text, ":#{emoji}:") end)
|
||||
end
|
||||
|
||||
def get_emoji(_), do: []
|
||||
|
||||
@doc "Outputs a list of the emoji-Maps in a text"
|
||||
def get_emoji_map(text) when is_binary(text) do
|
||||
get_emoji(text)
|
||||
|> Enum.reduce(%{}, fn {name, file, _group}, acc ->
|
||||
Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
|
||||
end)
|
||||
end
|
||||
|
||||
def get_emoji_map(_), do: []
|
||||
|
||||
def html_escape({text, mentions, hashtags}, type) do
|
||||
{html_escape(text, type), mentions, hashtags}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ defmodule Pleroma.List do
|
|||
alias Pleroma.User
|
||||
|
||||
schema "lists" do
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
field(:title, :string)
|
||||
field(:following, {:array, :string}, default: [])
|
||||
field(:ap_id, :string)
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ defmodule Pleroma.Notification do
|
|||
|
||||
schema "notifications" do
|
||||
field(:seen, :boolean, default: false)
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:activity, Activity, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ defmodule Pleroma.Pagination do
|
|||
|
||||
def paginate(query, options, :offset) do
|
||||
query
|
||||
|> restrict(:order, options)
|
||||
|> restrict(:offset, options)
|
||||
|> restrict(:limit, options)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ defmodule Pleroma.PasswordResetToken do
|
|||
alias Pleroma.User
|
||||
|
||||
schema "password_reset_tokens" do
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
field(:token, :string)
|
||||
field(:used, :boolean, default: false)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ defmodule Pleroma.Registration do
|
|||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
@primary_key {:id, Pleroma.FlakeId, autogenerate: true}
|
||||
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
|
||||
|
||||
schema "registrations" do
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
field(:provider, :string)
|
||||
field(:uid, :string)
|
||||
field(:info, :map, default: %{})
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ defmodule Pleroma.ScheduledActivity do
|
|||
@min_offset :timer.minutes(5)
|
||||
|
||||
schema "scheduled_activities" do
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
field(:scheduled_at, :naive_datetime)
|
||||
field(:params, :map)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ defmodule Pleroma.ThreadMute do
|
|||
require Ecto.Query
|
||||
|
||||
schema "thread_mutes" do
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
field(:context, :string)
|
||||
end
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ defmodule Pleroma.ThreadMute do
|
|||
end
|
||||
|
||||
def query(user_id, context) do
|
||||
user_id = Pleroma.FlakeId.from_string(user_id)
|
||||
{:ok, user_id} = FlakeId.Ecto.CompatType.dump(user_id)
|
||||
|
||||
ThreadMute
|
||||
|> Ecto.Query.where(user_id: ^user_id)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ defmodule Pleroma.User do
|
|||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
@primary_key {:id, Pleroma.FlakeId, autogenerate: true}
|
||||
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
|
||||
|
||||
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
||||
@email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
|
||||
|
|
@ -106,9 +106,7 @@ defmodule Pleroma.User do
|
|||
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
|
||||
def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
|
||||
|
||||
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
|
||||
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
|
||||
|
|
@ -119,12 +117,9 @@ defmodule Pleroma.User do
|
|||
|
||||
def user_info(%User{} = user, args \\ %{}) do
|
||||
following_count =
|
||||
if args[:following_count],
|
||||
do: args[:following_count],
|
||||
else: user.info.following_count || following_count(user)
|
||||
Map.get(args, :following_count, user.info.following_count || following_count(user))
|
||||
|
||||
follower_count =
|
||||
if args[:follower_count], do: args[:follower_count], else: user.info.follower_count
|
||||
follower_count = Map.get(args, :follower_count, user.info.follower_count)
|
||||
|
||||
%{
|
||||
note_count: user.info.note_count,
|
||||
|
|
@ -137,12 +132,11 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def follow_state(%User{} = user, %User{} = target) do
|
||||
follow_activity = Utils.fetch_latest_follow(user, target)
|
||||
|
||||
if follow_activity,
|
||||
do: follow_activity.data["state"],
|
||||
case Utils.fetch_latest_follow(user, target) do
|
||||
%{data: %{"state" => state}} -> state
|
||||
# Ideally this would be nil, but then Cachex does not commit the value
|
||||
else: false
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
def get_cached_follow_state(user, target) do
|
||||
|
|
@ -152,11 +146,7 @@ defmodule Pleroma.User do
|
|||
|
||||
@spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
|
||||
def set_follow_state_cache(user_ap_id, target_ap_id, state) do
|
||||
Cachex.put(
|
||||
:user_cache,
|
||||
"follow_state:#{user_ap_id}|#{target_ap_id}",
|
||||
state
|
||||
)
|
||||
Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
|
||||
end
|
||||
|
||||
def set_info_cache(user, args) do
|
||||
|
|
@ -197,34 +187,25 @@ defmodule Pleroma.User do
|
|||
|> truncate_if_exists(:name, name_limit)
|
||||
|> truncate_if_exists(:bio, bio_limit)
|
||||
|
||||
info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info])
|
||||
|
||||
changes =
|
||||
%User{}
|
||||
changeset =
|
||||
%User{local: false}
|
||||
|> cast(params, [:bio, :name, :ap_id, :nickname, :avatar])
|
||||
|> validate_required([:name, :ap_id])
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_format(:nickname, @email_regex)
|
||||
|> validate_length(:bio, max: bio_limit)
|
||||
|> validate_length(:name, max: name_limit)
|
||||
|> put_change(:local, false)
|
||||
|> put_embed(:info, info_cng)
|
||||
|> change_info(&User.Info.remote_user_creation(&1, params[:info]))
|
||||
|
||||
if changes.valid? do
|
||||
case info_cng.changes[:source_data] do
|
||||
%{"followers" => followers, "following" => following} ->
|
||||
changes
|
||||
|> put_change(:follower_address, followers)
|
||||
|> put_change(:following_address, following)
|
||||
case params[:info][:source_data] do
|
||||
%{"followers" => followers, "following" => following} ->
|
||||
changeset
|
||||
|> put_change(:follower_address, followers)
|
||||
|> put_change(:following_address, following)
|
||||
|
||||
_ ->
|
||||
followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
|
||||
|
||||
changes
|
||||
|> put_change(:follower_address, followers)
|
||||
end
|
||||
else
|
||||
changes
|
||||
_ ->
|
||||
followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
|
||||
put_change(changeset, :follower_address, followers)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -245,7 +226,6 @@ defmodule Pleroma.User do
|
|||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||
|
||||
params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
|
||||
info_cng = User.Info.user_upgrade(struct.info, params[:info], remote?)
|
||||
|
||||
struct
|
||||
|> cast(params, [
|
||||
|
|
@ -260,7 +240,7 @@ defmodule Pleroma.User do
|
|||
|> validate_format(:nickname, local_nickname_regex())
|
||||
|> validate_length(:bio, max: bio_limit)
|
||||
|> validate_length(:name, max: name_limit)
|
||||
|> put_embed(:info, info_cng)
|
||||
|> change_info(&User.Info.user_upgrade(&1, params[:info], remote?))
|
||||
end
|
||||
|
||||
def password_update_changeset(struct, params) do
|
||||
|
|
@ -311,43 +291,39 @@ defmodule Pleroma.User do
|
|||
opts[:need_confirmation]
|
||||
end
|
||||
|
||||
info_change =
|
||||
User.Info.confirmation_changeset(%User.Info{}, need_confirmation: need_confirmation?)
|
||||
struct
|
||||
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|
||||
|> validate_required([:name, :nickname, :password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|> unique_constraint(:email)
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
|
||||
|> validate_format(:nickname, local_nickname_regex())
|
||||
|> validate_format(:email, @email_regex)
|
||||
|> validate_length(:bio, max: bio_limit)
|
||||
|> validate_length(:name, min: 1, max: name_limit)
|
||||
|> change_info(&User.Info.confirmation_changeset(&1, need_confirmation: need_confirmation?))
|
||||
|> maybe_validate_required_email(opts[:external])
|
||||
|> put_password_hash
|
||||
|> put_ap_id()
|
||||
|> unique_constraint(:ap_id)
|
||||
|> put_following_and_follower_address()
|
||||
end
|
||||
|
||||
changeset =
|
||||
struct
|
||||
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|
||||
|> validate_required([:name, :nickname, :password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|> unique_constraint(:email)
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
|
||||
|> validate_format(:nickname, local_nickname_regex())
|
||||
|> validate_format(:email, @email_regex)
|
||||
|> validate_length(:bio, max: bio_limit)
|
||||
|> validate_length(:name, min: 1, max: name_limit)
|
||||
|> put_change(:info, info_change)
|
||||
def maybe_validate_required_email(changeset, true), do: changeset
|
||||
def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
|
||||
|
||||
changeset =
|
||||
if opts[:external] do
|
||||
changeset
|
||||
else
|
||||
validate_required(changeset, [:email])
|
||||
end
|
||||
defp put_ap_id(changeset) do
|
||||
ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
|
||||
put_change(changeset, :ap_id, ap_id)
|
||||
end
|
||||
|
||||
if changeset.valid? do
|
||||
ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
|
||||
followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]})
|
||||
defp put_following_and_follower_address(changeset) do
|
||||
followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
|
||||
|
||||
changeset
|
||||
|> put_password_hash
|
||||
|> put_change(:ap_id, ap_id)
|
||||
|> unique_constraint(:ap_id)
|
||||
|> put_change(:following, [followers])
|
||||
|> put_change(:follower_address, followers)
|
||||
else
|
||||
changeset
|
||||
end
|
||||
changeset
|
||||
|> put_change(:following, [followers])
|
||||
|> put_change(:follower_address, followers)
|
||||
end
|
||||
|
||||
defp autofollow_users(user) do
|
||||
|
|
@ -362,9 +338,8 @@ defmodule Pleroma.User do
|
|||
|
||||
@doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
|
||||
def register(%Ecto.Changeset{} = changeset) do
|
||||
with {:ok, user} <- Repo.insert(changeset),
|
||||
{:ok, user} <- post_register_action(user) do
|
||||
{:ok, user}
|
||||
with {:ok, user} <- Repo.insert(changeset) do
|
||||
post_register_action(user)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -410,7 +385,7 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def maybe_direct_follow(%User{} = follower, %User{} = followed) do
|
||||
if not User.ap_enabled?(followed) do
|
||||
if not ap_enabled?(followed) do
|
||||
follow(follower, followed)
|
||||
else
|
||||
{:ok, follower}
|
||||
|
|
@ -443,9 +418,7 @@ defmodule Pleroma.User do
|
|||
|
||||
{1, [follower]} = Repo.update_all(q, [])
|
||||
|
||||
Enum.each(followeds, fn followed ->
|
||||
update_follower_count(followed)
|
||||
end)
|
||||
Enum.each(followeds, &update_follower_count/1)
|
||||
|
||||
set_cache(follower)
|
||||
end
|
||||
|
|
@ -560,8 +533,6 @@ defmodule Pleroma.User do
|
|||
def update_and_set_cache(changeset) do
|
||||
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
|
||||
set_cache(user)
|
||||
else
|
||||
e -> e
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -598,9 +569,7 @@ defmodule Pleroma.User do
|
|||
key = "nickname:#{nickname}"
|
||||
|
||||
Cachex.fetch!(:user_cache, key, fn ->
|
||||
user_result = get_or_fetch_by_nickname(nickname)
|
||||
|
||||
case user_result do
|
||||
case get_or_fetch_by_nickname(nickname) do
|
||||
{:ok, user} -> {:commit, user}
|
||||
{:error, _error} -> {:ignore, nil}
|
||||
end
|
||||
|
|
@ -611,7 +580,7 @@ defmodule Pleroma.User do
|
|||
restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
|
||||
|
||||
cond do
|
||||
is_integer(nickname_or_id) or Pleroma.FlakeId.is_flake_id?(nickname_or_id) ->
|
||||
is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
|
||||
get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
|
||||
|
||||
restrict_to_local == false ->
|
||||
|
|
@ -640,13 +609,11 @@ defmodule Pleroma.User do
|
|||
|
||||
def get_cached_user_info(user) do
|
||||
key = "user_info:#{user.id}"
|
||||
Cachex.fetch!(:user_cache, key, fn _ -> user_info(user) end)
|
||||
Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
|
||||
end
|
||||
|
||||
def fetch_by_nickname(nickname) do
|
||||
ap_try = ActivityPub.make_user_from_nickname(nickname)
|
||||
|
||||
case ap_try do
|
||||
case ActivityPub.make_user_from_nickname(nickname) do
|
||||
{:ok, user} -> {:ok, user}
|
||||
_ -> OStatus.make_user(nickname)
|
||||
end
|
||||
|
|
@ -681,7 +648,8 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def get_followers_query(user, page) do
|
||||
from(u in get_followers_query(user, nil))
|
||||
user
|
||||
|> get_followers_query(nil)
|
||||
|> User.Query.paginate(page, 20)
|
||||
end
|
||||
|
||||
|
|
@ -690,25 +658,24 @@ defmodule Pleroma.User do
|
|||
|
||||
@spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
|
||||
def get_followers(user, page \\ nil) do
|
||||
q = get_followers_query(user, page)
|
||||
|
||||
{:ok, Repo.all(q)}
|
||||
user
|
||||
|> get_followers_query(page)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
|
||||
def get_external_followers(user, page \\ nil) do
|
||||
q =
|
||||
user
|
||||
|> get_followers_query(page)
|
||||
|> User.Query.build(%{external: true})
|
||||
|
||||
{:ok, Repo.all(q)}
|
||||
user
|
||||
|> get_followers_query(page)
|
||||
|> User.Query.build(%{external: true})
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def get_followers_ids(user, page \\ nil) do
|
||||
q = get_followers_query(user, page)
|
||||
|
||||
Repo.all(from(u in q, select: u.id))
|
||||
user
|
||||
|> get_followers_query(page)
|
||||
|> select([u], u.id)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
|
||||
|
|
@ -717,7 +684,8 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def get_friends_query(user, page) do
|
||||
from(u in get_friends_query(user, nil))
|
||||
user
|
||||
|> get_friends_query(nil)
|
||||
|> User.Query.paginate(page, 20)
|
||||
end
|
||||
|
||||
|
|
@ -725,28 +693,27 @@ defmodule Pleroma.User do
|
|||
def get_friends_query(user), do: get_friends_query(user, nil)
|
||||
|
||||
def get_friends(user, page \\ nil) do
|
||||
q = get_friends_query(user, page)
|
||||
|
||||
{:ok, Repo.all(q)}
|
||||
user
|
||||
|> get_friends_query(page)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def get_friends_ids(user, page \\ nil) do
|
||||
q = get_friends_query(user, page)
|
||||
|
||||
Repo.all(from(u in q, select: u.id))
|
||||
user
|
||||
|> get_friends_query(page)
|
||||
|> select([u], u.id)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
|
||||
def get_follow_requests(%User{} = user) do
|
||||
users =
|
||||
Activity.follow_requests_for_actor(user)
|
||||
|> join(:inner, [a], u in User, on: a.actor == u.ap_id)
|
||||
|> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
|
||||
|> group_by([a, u], u.id)
|
||||
|> select([a, u], u)
|
||||
|> Repo.all()
|
||||
|
||||
{:ok, users}
|
||||
user
|
||||
|> Activity.follow_requests_for_actor()
|
||||
|> join(:inner, [a], u in User, on: a.actor == u.ap_id)
|
||||
|> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
|
||||
|> group_by([a, u], u.id)
|
||||
|> select([a, u], u)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def increase_note_count(%User{} = user) do
|
||||
|
|
@ -792,21 +759,15 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def update_note_count(%User{} = user) do
|
||||
note_count_query =
|
||||
note_count =
|
||||
from(
|
||||
a in Object,
|
||||
where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
|
||||
select: count(a.id)
|
||||
)
|
||||
|> Repo.one()
|
||||
|
||||
note_count = Repo.one(note_count_query)
|
||||
|
||||
info_cng = User.Info.set_note_count(user.info, note_count)
|
||||
|
||||
user
|
||||
|> change()
|
||||
|> put_embed(:info, info_cng)
|
||||
|> update_and_set_cache()
|
||||
update_info(user, &User.Info.set_note_count(&1, note_count))
|
||||
end
|
||||
|
||||
def update_mascot(user, url) do
|
||||
|
|
@ -836,17 +797,7 @@ defmodule Pleroma.User do
|
|||
|
||||
def fetch_follow_information(user) do
|
||||
with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
|
||||
info_cng = User.Info.follow_information_update(user.info, info)
|
||||
|
||||
changeset =
|
||||
user
|
||||
|> change()
|
||||
|> put_embed(:info, info_cng)
|
||||
|
||||
update_and_set_cache(changeset)
|
||||
else
|
||||
{:error, _} = e -> e
|
||||
e -> {:error, e}
|
||||
update_info(user, &User.Info.follow_information_update(&1, info))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -920,60 +871,28 @@ defmodule Pleroma.User do
|
|||
|
||||
@spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
|
||||
def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
|
||||
info = muter.info
|
||||
|
||||
info_cng =
|
||||
User.Info.add_to_mutes(info, ap_id)
|
||||
|> User.Info.add_to_muted_notifications(info, ap_id, notifications?)
|
||||
|
||||
cng =
|
||||
change(muter)
|
||||
|> put_embed(:info, info_cng)
|
||||
|
||||
update_and_set_cache(cng)
|
||||
update_info(muter, &User.Info.add_to_mutes(&1, ap_id, notifications?))
|
||||
end
|
||||
|
||||
def unmute(muter, %{ap_id: ap_id}) do
|
||||
info = muter.info
|
||||
|
||||
info_cng =
|
||||
User.Info.remove_from_mutes(info, ap_id)
|
||||
|> User.Info.remove_from_muted_notifications(info, ap_id)
|
||||
|
||||
cng =
|
||||
change(muter)
|
||||
|> put_embed(:info, info_cng)
|
||||
|
||||
update_and_set_cache(cng)
|
||||
update_info(muter, &User.Info.remove_from_mutes(&1, ap_id))
|
||||
end
|
||||
|
||||
def subscribe(subscriber, %{ap_id: ap_id}) do
|
||||
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
|
||||
|
||||
with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
|
||||
blocked = blocks?(subscribed, subscriber) and deny_follow_blocked
|
||||
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
|
||||
|
||||
if blocked do
|
||||
if blocks?(subscribed, subscriber) and deny_follow_blocked do
|
||||
{:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
|
||||
else
|
||||
info_cng =
|
||||
subscribed.info
|
||||
|> User.Info.add_to_subscribers(subscriber.ap_id)
|
||||
|
||||
change(subscribed)
|
||||
|> put_embed(:info, info_cng)
|
||||
|> update_and_set_cache()
|
||||
update_info(subscribed, &User.Info.add_to_subscribers(&1, subscriber.ap_id))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
|
||||
with %User{} = user <- get_cached_by_ap_id(ap_id) do
|
||||
info_cng = User.Info.remove_from_subscribers(user.info, unsubscriber.ap_id)
|
||||
|
||||
change(user)
|
||||
|> put_embed(:info, info_cng)
|
||||
|> update_and_set_cache()
|
||||
update_info(user, &User.Info.remove_from_subscribers(&1, unsubscriber.ap_id))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1002,21 +921,11 @@ defmodule Pleroma.User do
|
|||
blocker
|
||||
end
|
||||
|
||||
if following?(blocked, blocker) do
|
||||
unfollow(blocked, blocker)
|
||||
end
|
||||
if following?(blocked, blocker), do: unfollow(blocked, blocker)
|
||||
|
||||
{:ok, blocker} = update_follower_count(blocker)
|
||||
|
||||
info_cng =
|
||||
blocker.info
|
||||
|> User.Info.add_to_block(ap_id)
|
||||
|
||||
cng =
|
||||
change(blocker)
|
||||
|> put_embed(:info, info_cng)
|
||||
|
||||
update_and_set_cache(cng)
|
||||
update_info(blocker, &User.Info.add_to_block(&1, ap_id))
|
||||
end
|
||||
|
||||
# helper to handle the block given only an actor's AP id
|
||||
|
|
@ -1025,15 +934,7 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def unblock(blocker, %{ap_id: ap_id}) do
|
||||
info_cng =
|
||||
blocker.info
|
||||
|> User.Info.remove_from_block(ap_id)
|
||||
|
||||
cng =
|
||||
change(blocker)
|
||||
|> put_embed(:info, info_cng)
|
||||
|
||||
update_and_set_cache(cng)
|
||||
update_info(blocker, &User.Info.remove_from_block(&1, ap_id))
|
||||
end
|
||||
|
||||
def mutes?(nil, _), do: false
|
||||
|
|
@ -1090,27 +991,11 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def block_domain(user, domain) do
|
||||
info_cng =
|
||||
user.info
|
||||
|> User.Info.add_to_domain_block(domain)
|
||||
|
||||
cng =
|
||||
change(user)
|
||||
|> put_embed(:info, info_cng)
|
||||
|
||||
update_and_set_cache(cng)
|
||||
update_info(user, &User.Info.add_to_domain_block(&1, domain))
|
||||
end
|
||||
|
||||
def unblock_domain(user, domain) do
|
||||
info_cng =
|
||||
user.info
|
||||
|> User.Info.remove_from_domain_block(domain)
|
||||
|
||||
cng =
|
||||
change(user)
|
||||
|> put_embed(:info, info_cng)
|
||||
|
||||
update_and_set_cache(cng)
|
||||
update_info(user, &User.Info.remove_from_domain_block(&1, domain))
|
||||
end
|
||||
|
||||
def deactivate_async(user, status \\ true) do
|
||||
|
|
@ -1118,28 +1003,16 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def deactivate(%User{} = user, status \\ true) do
|
||||
info_cng = User.Info.set_activation_status(user.info, status)
|
||||
|
||||
with {:ok, friends} <- User.get_friends(user),
|
||||
{:ok, followers} <- User.get_followers(user),
|
||||
{:ok, user} <-
|
||||
user
|
||||
|> change()
|
||||
|> put_embed(:info, info_cng)
|
||||
|> update_and_set_cache() do
|
||||
Enum.each(followers, &invalidate_cache(&1))
|
||||
Enum.each(friends, &update_follower_count(&1))
|
||||
with {:ok, user} <- update_info(user, &User.Info.set_activation_status(&1, status)) do
|
||||
Enum.each(get_followers(user), &invalidate_cache/1)
|
||||
Enum.each(get_friends(user), &update_follower_count/1)
|
||||
|
||||
{:ok, user}
|
||||
end
|
||||
end
|
||||
|
||||
def update_notification_settings(%User{} = user, settings \\ %{}) do
|
||||
info_changeset = User.Info.update_notification_settings(user.info, settings)
|
||||
|
||||
change(user)
|
||||
|> put_embed(:info, info_changeset)
|
||||
|> update_and_set_cache()
|
||||
update_info(user, &User.Info.update_notification_settings(&1, settings))
|
||||
end
|
||||
|
||||
def delete(%User{} = user) do
|
||||
|
|
@ -1153,18 +1026,18 @@ defmodule Pleroma.User do
|
|||
{:ok, _user} = ActivityPub.delete(user)
|
||||
|
||||
# Remove all relationships
|
||||
{:ok, followers} = User.get_followers(user)
|
||||
|
||||
Enum.each(followers, fn follower ->
|
||||
user
|
||||
|> get_followers()
|
||||
|> Enum.each(fn follower ->
|
||||
ActivityPub.unfollow(follower, user)
|
||||
User.unfollow(follower, user)
|
||||
unfollow(follower, user)
|
||||
end)
|
||||
|
||||
{:ok, friends} = User.get_friends(user)
|
||||
|
||||
Enum.each(friends, fn followed ->
|
||||
user
|
||||
|> get_friends()
|
||||
|> Enum.each(fn followed ->
|
||||
ActivityPub.unfollow(user, followed)
|
||||
User.unfollow(user, followed)
|
||||
unfollow(user, followed)
|
||||
end)
|
||||
|
||||
delete_user_activities(user)
|
||||
|
|
@ -1176,13 +1049,11 @@ defmodule Pleroma.User do
|
|||
def perform(:fetch_initial_posts, %User{} = user) do
|
||||
pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
|
||||
|
||||
Enum.each(
|
||||
# Insert all the posts in reverse order, so they're in the right order on the timeline
|
||||
Enum.reverse(Utils.fetch_ordered_collection(user.info.source_data["outbox"], pages)),
|
||||
&Pleroma.Web.Federator.incoming_ap_doc/1
|
||||
)
|
||||
|
||||
{:ok, user}
|
||||
# Insert all the posts in reverse order, so they're in the right order on the timeline
|
||||
user.info.source_data["outbox"]
|
||||
|> Utils.fetch_ordered_collection(pages)
|
||||
|> Enum.reverse()
|
||||
|> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
|
||||
end
|
||||
|
||||
def perform(:deactivate_async, user, status), do: deactivate(user, status)
|
||||
|
|
@ -1268,16 +1139,12 @@ defmodule Pleroma.User do
|
|||
})
|
||||
end
|
||||
|
||||
def delete_user_activities(%User{ap_id: ap_id} = user) do
|
||||
def delete_user_activities(%User{ap_id: ap_id}) do
|
||||
ap_id
|
||||
|> Activity.Queries.by_actor()
|
||||
|> RepoStreamer.chunk_stream(50)
|
||||
|> Stream.each(fn activities ->
|
||||
Enum.each(activities, &delete_activity(&1))
|
||||
end)
|
||||
|> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
|
||||
|> Stream.run()
|
||||
|
||||
{:ok, user}
|
||||
end
|
||||
|
||||
defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
|
||||
|
|
@ -1287,17 +1154,19 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
|
||||
user = get_cached_by_ap_id(activity.actor)
|
||||
object = Object.normalize(activity)
|
||||
|
||||
ActivityPub.unlike(user, object)
|
||||
activity.actor
|
||||
|> get_cached_by_ap_id()
|
||||
|> ActivityPub.unlike(object)
|
||||
end
|
||||
|
||||
defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
|
||||
user = get_cached_by_ap_id(activity.actor)
|
||||
object = Object.normalize(activity)
|
||||
|
||||
ActivityPub.unannounce(user, object)
|
||||
activity.actor
|
||||
|> get_cached_by_ap_id()
|
||||
|> ActivityPub.unannounce(object)
|
||||
end
|
||||
|
||||
defp delete_activity(_activity), do: "Doing nothing"
|
||||
|
|
@ -1309,9 +1178,7 @@ defmodule Pleroma.User do
|
|||
def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
|
||||
|
||||
def fetch_by_ap_id(ap_id) do
|
||||
ap_try = ActivityPub.make_user_from_ap_id(ap_id)
|
||||
|
||||
case ap_try do
|
||||
case ActivityPub.make_user_from_ap_id(ap_id) do
|
||||
{:ok, user} ->
|
||||
{:ok, user}
|
||||
|
||||
|
|
@ -1326,7 +1193,7 @@ defmodule Pleroma.User do
|
|||
def get_or_fetch_by_ap_id(ap_id) do
|
||||
user = get_cached_by_ap_id(ap_id)
|
||||
|
||||
if !is_nil(user) and !User.needs_update?(user) do
|
||||
if !is_nil(user) and !needs_update?(user) do
|
||||
{:ok, user}
|
||||
else
|
||||
# Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
|
||||
|
|
@ -1346,19 +1213,20 @@ defmodule Pleroma.User do
|
|||
|
||||
@doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
|
||||
def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
|
||||
if user = get_cached_by_ap_id(uri) do
|
||||
with %User{} = user <- get_cached_by_ap_id(uri) do
|
||||
user
|
||||
else
|
||||
changes =
|
||||
%User{info: %User.Info{}}
|
||||
|> cast(%{}, [:ap_id, :nickname, :local])
|
||||
|> put_change(:ap_id, uri)
|
||||
|> put_change(:nickname, nickname)
|
||||
|> put_change(:local, true)
|
||||
|> put_change(:follower_address, uri <> "/followers")
|
||||
_ ->
|
||||
{:ok, user} =
|
||||
%User{info: %User.Info{}}
|
||||
|> cast(%{}, [:ap_id, :nickname, :local])
|
||||
|> put_change(:ap_id, uri)
|
||||
|> put_change(:nickname, nickname)
|
||||
|> put_change(:local, true)
|
||||
|> put_change(:follower_address, uri <> "/followers")
|
||||
|> Repo.insert()
|
||||
|
||||
{:ok, user} = Repo.insert(changes)
|
||||
user
|
||||
user
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1415,23 +1283,21 @@ defmodule Pleroma.User do
|
|||
# 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 <- User.get_cached_by_id(a.id),
|
||||
%User{} = b <- User.get_cached_by_id(b.id) do
|
||||
with %User{} = a <- get_cached_by_id(a.id),
|
||||
%User{} = b <- get_cached_by_id(b.id) do
|
||||
{:ok, a, b}
|
||||
else
|
||||
_e ->
|
||||
:error
|
||||
nil -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
|
||||
with :ok <- :timer.sleep(timeout),
|
||||
%User{} = a <- User.get_cached_by_id(a.id),
|
||||
%User{} = b <- User.get_cached_by_id(b.id) do
|
||||
%User{} = a <- get_cached_by_id(a.id),
|
||||
%User{} = b <- get_cached_by_id(b.id) do
|
||||
{:ok, a, b}
|
||||
else
|
||||
_e ->
|
||||
:error
|
||||
nil -> :error
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1493,7 +1359,7 @@ defmodule Pleroma.User do
|
|||
defp normalize_tags(tags) do
|
||||
[tags]
|
||||
|> List.flatten()
|
||||
|> Enum.map(&String.downcase(&1))
|
||||
|> Enum.map(&String.downcase/1)
|
||||
end
|
||||
|
||||
defp local_nickname_regex do
|
||||
|
|
@ -1586,11 +1452,7 @@ defmodule Pleroma.User do
|
|||
@spec switch_email_notifications(t(), String.t(), boolean()) ::
|
||||
{:ok, t()} | {:error, Ecto.Changeset.t()}
|
||||
def switch_email_notifications(user, type, status) do
|
||||
info = Pleroma.User.Info.update_email_notifications(user.info, %{type => status})
|
||||
|
||||
change(user)
|
||||
|> put_embed(:info, info)
|
||||
|> update_and_set_cache()
|
||||
update_info(user, &User.Info.update_email_notifications(&1, %{type => status}))
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
@ -1612,13 +1474,8 @@ defmodule Pleroma.User do
|
|||
def toggle_confirmation(%User{} = user) do
|
||||
need_confirmation? = !user.info.confirmation_pending
|
||||
|
||||
info_changeset =
|
||||
User.Info.confirmation_changeset(user.info, need_confirmation: need_confirmation?)
|
||||
|
||||
user
|
||||
|> change()
|
||||
|> put_embed(:info, info_changeset)
|
||||
|> update_and_set_cache()
|
||||
|> update_info(&User.Info.confirmation_changeset(&1, need_confirmation: need_confirmation?))
|
||||
end
|
||||
|
||||
def get_mascot(%{info: %{mascot: %{} = mascot}}) when not is_nil(mascot) do
|
||||
|
|
@ -1641,16 +1498,11 @@ defmodule Pleroma.User do
|
|||
}
|
||||
end
|
||||
|
||||
def ensure_keys_present(%User{info: info} = user) do
|
||||
if info.keys do
|
||||
{:ok, user}
|
||||
else
|
||||
{:ok, pem} = Keys.generate_rsa_pem()
|
||||
def ensure_keys_present(%{info: %{keys: keys}} = user) when not is_nil(keys), do: {:ok, user}
|
||||
|
||||
user
|
||||
|> Ecto.Changeset.change()
|
||||
|> Ecto.Changeset.put_embed(:info, User.Info.set_keys(info, pem))
|
||||
|> update_and_set_cache()
|
||||
def ensure_keys_present(%User{} = user) do
|
||||
with {:ok, pem} <- Keys.generate_rsa_pem() do
|
||||
update_info(user, &User.Info.set_keys(&1, pem))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1696,4 +1548,26 @@ defmodule Pleroma.User do
|
|||
|> validate_format(:email, @email_regex)
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes `user.info` and returns the user changeset.
|
||||
|
||||
`fun` is called with the `user.info`.
|
||||
"""
|
||||
def change_info(user, fun) do
|
||||
changeset = change(user)
|
||||
info = get_field(changeset, :info) || %User.Info{}
|
||||
put_embed(changeset, :info, fun.(info))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates `user.info` and sets cache.
|
||||
|
||||
`fun` is called with the `user.info`.
|
||||
"""
|
||||
def update_info(user, fun) do
|
||||
user
|
||||
|> change_info(fun)
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ defmodule Pleroma.User.Info do
|
|||
field(:pleroma_settings_store, :map, default: %{})
|
||||
field(:fields, {:array, :map}, default: nil)
|
||||
field(:raw_fields, {:array, :map}, default: [])
|
||||
field(:discoverable, :boolean, default: false)
|
||||
|
||||
field(:notification_settings, :map,
|
||||
default: %{
|
||||
|
|
@ -187,16 +188,11 @@ defmodule Pleroma.User.Info do
|
|||
|> validate_required([:subscribers])
|
||||
end
|
||||
|
||||
@spec add_to_mutes(Info.t(), String.t()) :: Changeset.t()
|
||||
def add_to_mutes(info, muted) do
|
||||
set_mutes(info, Enum.uniq([muted | info.mutes]))
|
||||
end
|
||||
|
||||
@spec add_to_muted_notifications(Changeset.t(), Info.t(), String.t(), boolean()) ::
|
||||
Changeset.t()
|
||||
def add_to_muted_notifications(changeset, info, muted, notifications?) do
|
||||
set_notification_mutes(
|
||||
changeset,
|
||||
@spec add_to_mutes(Info.t(), String.t(), boolean()) :: Changeset.t()
|
||||
def add_to_mutes(info, muted, notifications?) do
|
||||
info
|
||||
|> set_mutes(Enum.uniq([muted | info.mutes]))
|
||||
|> set_notification_mutes(
|
||||
Enum.uniq([muted | info.muted_notifications]),
|
||||
notifications?
|
||||
)
|
||||
|
|
@ -204,12 +200,9 @@ defmodule Pleroma.User.Info do
|
|||
|
||||
@spec remove_from_mutes(Info.t(), String.t()) :: Changeset.t()
|
||||
def remove_from_mutes(info, muted) do
|
||||
set_mutes(info, List.delete(info.mutes, muted))
|
||||
end
|
||||
|
||||
@spec remove_from_muted_notifications(Changeset.t(), Info.t(), String.t()) :: Changeset.t()
|
||||
def remove_from_muted_notifications(changeset, info, muted) do
|
||||
set_notification_mutes(changeset, List.delete(info.muted_notifications, muted), true)
|
||||
info
|
||||
|> set_mutes(List.delete(info.mutes, muted))
|
||||
|> set_notification_mutes(List.delete(info.muted_notifications, muted), true)
|
||||
end
|
||||
|
||||
def add_to_block(info, blocked) do
|
||||
|
|
@ -277,7 +270,8 @@ defmodule Pleroma.User.Info do
|
|||
:hide_follows_count,
|
||||
:follower_count,
|
||||
:fields,
|
||||
:following_count
|
||||
:following_count,
|
||||
:discoverable
|
||||
])
|
||||
|> validate_fields(true)
|
||||
end
|
||||
|
|
@ -295,6 +289,7 @@ defmodule Pleroma.User.Info do
|
|||
:hide_follows,
|
||||
:fields,
|
||||
:hide_followers,
|
||||
:discoverable,
|
||||
:hide_followers_count,
|
||||
:hide_follows_count
|
||||
])
|
||||
|
|
@ -318,7 +313,8 @@ defmodule Pleroma.User.Info do
|
|||
:skip_thread_containment,
|
||||
:fields,
|
||||
:raw_fields,
|
||||
:pleroma_settings_store
|
||||
:pleroma_settings_store,
|
||||
:discoverable
|
||||
])
|
||||
|> validate_fields()
|
||||
end
|
||||
|
|
|
|||
|
|
@ -510,7 +510,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
|
||||
@spec fetch_latest_activity_id_for_context(String.t(), keyword() | map()) ::
|
||||
Pleroma.FlakeId.t() | nil
|
||||
FlakeId.Ecto.CompatType.t() | nil
|
||||
def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
|
||||
context
|
||||
|> fetch_activities_for_context_query(Map.merge(%{"skip_preload" => true}, opts))
|
||||
|
|
@ -519,13 +519,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> Repo.one()
|
||||
end
|
||||
|
||||
def fetch_public_activities(opts \\ %{}) do
|
||||
def fetch_public_activities(opts \\ %{}, pagination \\ :keyset) do
|
||||
opts = Map.drop(opts, ["user"])
|
||||
|
||||
[Pleroma.Constants.as_public()]
|
||||
|> fetch_activities_query(opts)
|
||||
|> restrict_unlisted()
|
||||
|> Pagination.fetch_paginated(opts)
|
||||
|> Pagination.fetch_paginated(opts, pagination)
|
||||
|> Enum.reverse()
|
||||
end
|
||||
|
||||
|
|
@ -834,7 +834,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
defp restrict_muted_reblogs(query, _), do: query
|
||||
|
||||
defp exclude_poll_votes(query, %{"include_poll_votes" => "true"}), do: query
|
||||
defp exclude_poll_votes(query, %{"include_poll_votes" => true}), do: query
|
||||
|
||||
defp exclude_poll_votes(query, _) do
|
||||
if has_named_binding?(query, :object) do
|
||||
|
|
@ -918,11 +918,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> exclude_poll_votes(opts)
|
||||
end
|
||||
|
||||
def fetch_activities(recipients, opts \\ %{}) do
|
||||
def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
|
||||
list_memberships = Pleroma.List.memberships(opts["user"])
|
||||
|
||||
fetch_activities_query(recipients ++ list_memberships, opts)
|
||||
|> Pagination.fetch_paginated(opts)
|
||||
|> Pagination.fetch_paginated(opts, pagination)
|
||||
|> Enum.reverse()
|
||||
|> maybe_update_cc(list_memberships, opts["user"])
|
||||
end
|
||||
|
|
@ -953,10 +953,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
)
|
||||
end
|
||||
|
||||
def fetch_activities_bounded(recipients, recipients_with_public, opts \\ %{}) do
|
||||
def fetch_activities_bounded(
|
||||
recipients,
|
||||
recipients_with_public,
|
||||
opts \\ %{},
|
||||
pagination \\ :keyset
|
||||
) do
|
||||
fetch_activities_query([], opts)
|
||||
|> fetch_activities_bounded_query(recipients, recipients_with_public)
|
||||
|> Pagination.fetch_paginated(opts)
|
||||
|> Pagination.fetch_paginated(opts, pagination)
|
||||
|> Enum.reverse()
|
||||
end
|
||||
|
||||
|
|
@ -996,6 +1001,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
locked = data["manuallyApprovesFollowers"] || false
|
||||
data = Transmogrifier.maybe_fix_user_object(data)
|
||||
discoverable = data["discoverable"] || false
|
||||
|
||||
user_data = %{
|
||||
ap_id: data["id"],
|
||||
|
|
@ -1004,7 +1010,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
source_data: data,
|
||||
banner: banner,
|
||||
fields: fields,
|
||||
locked: locked
|
||||
locked: locked,
|
||||
discoverable: discoverable
|
||||
},
|
||||
avatar: avatar,
|
||||
name: data["name"],
|
||||
|
|
|
|||
|
|
@ -231,13 +231,43 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
end
|
||||
end
|
||||
|
||||
def outbox(conn, %{"nickname" => nickname} = params) do
|
||||
def outbox(conn, %{"nickname" => nickname, "page" => page?} = params)
|
||||
when page? in [true, "true"] do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||
{:ok, user} <- User.ensure_keys_present(user) do
|
||||
activities =
|
||||
if params["max_id"] do
|
||||
ActivityPub.fetch_user_activities(user, nil, %{
|
||||
"max_id" => params["max_id"],
|
||||
# This is a hack because postgres generates inefficient queries when filtering by
|
||||
# 'Answer', poll votes will be hidden by the visibility filter in this case anyway
|
||||
"include_poll_votes" => true,
|
||||
"limit" => 10
|
||||
})
|
||||
else
|
||||
ActivityPub.fetch_user_activities(user, nil, %{
|
||||
"limit" => 10,
|
||||
"include_poll_votes" => true
|
||||
})
|
||||
end
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/activity+json")
|
||||
|> put_view(UserView)
|
||||
|> render("activity_collection_page.json", %{
|
||||
activities: activities,
|
||||
iri: "#{user.ap_id}/outbox"
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def outbox(conn, %{"nickname" => nickname}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||
{:ok, user} <- User.ensure_keys_present(user) do
|
||||
conn
|
||||
|> put_resp_content_type("application/activity+json")
|
||||
|> put_view(UserView)
|
||||
|> render("outbox.json", %{user: user, max_id: params["max_id"]})
|
||||
|> render("activity_collection.json", %{iri: "#{user.ap_id}/outbox"})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -315,12 +345,37 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
|
||||
def read_inbox(
|
||||
%{assigns: %{user: %{nickname: nickname} = user}} = conn,
|
||||
%{"nickname" => nickname} = params
|
||||
) do
|
||||
%{"nickname" => nickname, "page" => page?} = params
|
||||
)
|
||||
when page? in [true, "true"] do
|
||||
activities =
|
||||
if params["max_id"] do
|
||||
ActivityPub.fetch_activities([user.ap_id | user.following], %{
|
||||
"max_id" => params["max_id"],
|
||||
"limit" => 10
|
||||
})
|
||||
else
|
||||
ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10})
|
||||
end
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/activity+json")
|
||||
|> put_view(UserView)
|
||||
|> render("inbox.json", user: user, max_id: params["max_id"])
|
||||
|> render("activity_collection_page.json", %{
|
||||
activities: activities,
|
||||
iri: "#{user.ap_id}/inbox"
|
||||
})
|
||||
end
|
||||
|
||||
def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{
|
||||
"nickname" => nickname
|
||||
}) do
|
||||
with {:ok, user} <- User.ensure_keys_present(user) do
|
||||
conn
|
||||
|> put_resp_content_type("application/activity+json")
|
||||
|> put_view(UserView)
|
||||
|> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"})
|
||||
end
|
||||
end
|
||||
|
||||
def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
|
||||
|
|
|
|||
|
|
@ -111,11 +111,11 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
|
||||
@spec recipients(User.t(), Activity.t()) :: list(User.t()) | []
|
||||
defp recipients(actor, activity) do
|
||||
{:ok, followers} =
|
||||
followers =
|
||||
if actor.follower_address in activity.recipients do
|
||||
User.get_external_followers(actor)
|
||||
else
|
||||
{:ok, []}
|
||||
[]
|
||||
end
|
||||
|
||||
fetchers =
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
alias Pleroma.Keys
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.Endpoint
|
||||
|
|
@ -107,7 +106,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
},
|
||||
"endpoints" => endpoints,
|
||||
"attachment" => fields,
|
||||
"tag" => (user.info.source_data["tag"] || []) ++ emoji_tags
|
||||
"tag" => (user.info.source_data["tag"] || []) ++ emoji_tags,
|
||||
"discoverable" => user.info.discoverable
|
||||
}
|
||||
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|
||||
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
|
||||
|
|
@ -210,20 +210,16 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render("outbox.json", %{user: user, max_id: max_qid}) do
|
||||
params = %{
|
||||
"limit" => "10"
|
||||
def render("activity_collection.json", %{iri: iri}) do
|
||||
%{
|
||||
"id" => iri,
|
||||
"type" => "OrderedCollection",
|
||||
"first" => "#{iri}?page=true"
|
||||
}
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
params =
|
||||
if max_qid != nil do
|
||||
Map.put(params, "max_id", max_qid)
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
activities = ActivityPub.fetch_user_activities(user, nil, params)
|
||||
|
||||
def render("activity_collection_page.json", %{activities: activities, iri: iri}) do
|
||||
# this is sorted chronologically, so first activity is the newest (max)
|
||||
{max_id, min_id, collection} =
|
||||
if length(activities) > 0 do
|
||||
|
|
@ -243,71 +239,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
}
|
||||
end
|
||||
|
||||
iri = "#{user.ap_id}/outbox"
|
||||
|
||||
page = %{
|
||||
"id" => "#{iri}?max_id=#{max_id}",
|
||||
%{
|
||||
"id" => "#{iri}?max_id=#{max_id}&page=true",
|
||||
"type" => "OrderedCollectionPage",
|
||||
"partOf" => iri,
|
||||
"orderedItems" => collection,
|
||||
"next" => "#{iri}?max_id=#{min_id}"
|
||||
"next" => "#{iri}?max_id=#{min_id}&page=true"
|
||||
}
|
||||
|
||||
if max_qid == nil do
|
||||
%{
|
||||
"id" => iri,
|
||||
"type" => "OrderedCollection",
|
||||
"first" => page
|
||||
}
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
else
|
||||
page |> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
end
|
||||
|
||||
def render("inbox.json", %{user: user, max_id: max_qid}) do
|
||||
params = %{
|
||||
"limit" => "10"
|
||||
}
|
||||
|
||||
params =
|
||||
if max_qid != nil do
|
||||
Map.put(params, "max_id", max_qid)
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||
|
||||
min_id = Enum.at(Enum.reverse(activities), 0).id
|
||||
max_id = Enum.at(activities, 0).id
|
||||
|
||||
collection =
|
||||
Enum.map(activities, fn act ->
|
||||
{:ok, data} = Transmogrifier.prepare_outgoing(act.data)
|
||||
data
|
||||
end)
|
||||
|
||||
iri = "#{user.ap_id}/inbox"
|
||||
|
||||
page = %{
|
||||
"id" => "#{iri}?max_id=#{max_id}",
|
||||
"type" => "OrderedCollectionPage",
|
||||
"partOf" => iri,
|
||||
"orderedItems" => collection,
|
||||
"next" => "#{iri}?max_id=#{min_id}"
|
||||
}
|
||||
|
||||
if max_qid == nil do
|
||||
%{
|
||||
"id" => iri,
|
||||
"type" => "OrderedCollection",
|
||||
"first" => page
|
||||
}
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
else
|
||||
page |> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def collection(collection, iri, page, show_items \\ true, total \\ nil) do
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
alias Pleroma.Web.AdminAPI.ReportView
|
||||
alias Pleroma.Web.AdminAPI.Search
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.Router
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||
|
||||
|
|
@ -254,18 +256,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
"nickname" => nickname
|
||||
})
|
||||
when permission_group in ["moderator", "admin"] do
|
||||
user = User.get_cached_by_nickname(nickname)
|
||||
info = Map.put(%{}, "is_" <> permission_group, true)
|
||||
|
||||
info =
|
||||
%{}
|
||||
|> Map.put("is_" <> permission_group, true)
|
||||
|
||||
info_cng = User.Info.admin_api_update(user.info, info)
|
||||
|
||||
cng =
|
||||
user
|
||||
|> Ecto.Changeset.change()
|
||||
|> Ecto.Changeset.put_embed(:info, info_cng)
|
||||
{:ok, user} =
|
||||
nickname
|
||||
|> User.get_cached_by_nickname()
|
||||
|> User.update_info(&User.Info.admin_api_update(&1, info))
|
||||
|
||||
ModerationLog.insert_log(%{
|
||||
action: "grant",
|
||||
|
|
@ -274,8 +270,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
permission: permission_group
|
||||
})
|
||||
|
||||
{:ok, _user} = User.update_and_set_cache(cng)
|
||||
|
||||
json(conn, info)
|
||||
end
|
||||
|
||||
|
|
@ -293,40 +287,33 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
})
|
||||
end
|
||||
|
||||
def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
|
||||
render_error(conn, :forbidden, "You can't revoke your own admin status.")
|
||||
end
|
||||
|
||||
def right_delete(
|
||||
%{assigns: %{user: %User{:nickname => admin_nickname} = admin}} = conn,
|
||||
%{assigns: %{user: admin}} = conn,
|
||||
%{
|
||||
"permission_group" => permission_group,
|
||||
"nickname" => nickname
|
||||
}
|
||||
)
|
||||
when permission_group in ["moderator", "admin"] do
|
||||
if admin_nickname == nickname do
|
||||
render_error(conn, :forbidden, "You can't revoke your own admin status.")
|
||||
else
|
||||
user = User.get_cached_by_nickname(nickname)
|
||||
info = Map.put(%{}, "is_" <> permission_group, false)
|
||||
|
||||
info =
|
||||
%{}
|
||||
|> Map.put("is_" <> permission_group, false)
|
||||
{:ok, user} =
|
||||
nickname
|
||||
|> User.get_cached_by_nickname()
|
||||
|> User.update_info(&User.Info.admin_api_update(&1, info))
|
||||
|
||||
info_cng = User.Info.admin_api_update(user.info, info)
|
||||
ModerationLog.insert_log(%{
|
||||
action: "revoke",
|
||||
actor: admin,
|
||||
subject: user,
|
||||
permission: permission_group
|
||||
})
|
||||
|
||||
cng =
|
||||
Ecto.Changeset.change(user)
|
||||
|> Ecto.Changeset.put_embed(:info, info_cng)
|
||||
|
||||
{:ok, _user} = User.update_and_set_cache(cng)
|
||||
|
||||
ModerationLog.insert_log(%{
|
||||
action: "revoke",
|
||||
actor: admin,
|
||||
subject: user,
|
||||
permission: permission_group
|
||||
})
|
||||
|
||||
json(conn, info)
|
||||
end
|
||||
json(conn, info)
|
||||
end
|
||||
|
||||
def right_delete(conn, _) do
|
||||
|
|
@ -450,7 +437,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
{:ok, token} = Pleroma.PasswordResetToken.create_token(user)
|
||||
|
||||
conn
|
||||
|> json(token.token)
|
||||
|> json(%{
|
||||
token: token.token,
|
||||
link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
|
||||
})
|
||||
end
|
||||
|
||||
@doc "Force password reset for a given user"
|
||||
|
|
@ -463,13 +453,17 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
end
|
||||
|
||||
def list_reports(conn, params) do
|
||||
{page, page_size} = page_params(params)
|
||||
|
||||
params =
|
||||
params
|
||||
|> Map.put("type", "Flag")
|
||||
|> Map.put("skip_preload", true)
|
||||
|> Map.put("total", true)
|
||||
|> Map.put("limit", page_size)
|
||||
|> Map.put("offset", (page - 1) * page_size)
|
||||
|
||||
reports = ActivityPub.fetch_activities([], params)
|
||||
reports = ActivityPub.fetch_activities([], params, :offset)
|
||||
|
||||
conn
|
||||
|> put_view(ReportView)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
alias Pleroma.Activity
|
||||
alias Pleroma.ActivityExpiration
|
||||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.Emoji
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.ThreadMute
|
||||
alias Pleroma.User
|
||||
|
|
@ -261,12 +261,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
sensitive,
|
||||
poll
|
||||
),
|
||||
object <-
|
||||
Map.put(
|
||||
object,
|
||||
"emoji",
|
||||
Map.merge(Formatter.get_emoji_map(full_payload), poll_emoji)
|
||||
) do
|
||||
object <- put_emoji(object, full_payload, poll_emoji) do
|
||||
preview? = Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
|
||||
direct? = visibility == "direct"
|
||||
|
||||
|
|
@ -300,18 +295,25 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
# parse and put emoji to object data
|
||||
defp put_emoji(map, text, emojis) do
|
||||
Map.put(
|
||||
map,
|
||||
"emoji",
|
||||
Map.merge(Emoji.Formatter.get_emoji_map(text), emojis)
|
||||
)
|
||||
end
|
||||
|
||||
# Updates the emojis for a user based on their profile
|
||||
def update(user) do
|
||||
emoji = emoji_from_profile(user)
|
||||
source_data = user.info |> Map.get(:source_data, {}) |> Map.put("tag", emoji)
|
||||
|
||||
user =
|
||||
with emoji <- emoji_from_profile(user),
|
||||
source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji),
|
||||
info_cng <- User.Info.set_source_data(user.info, source_data),
|
||||
change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
|
||||
{:ok, user} <- User.update_and_set_cache(change) do
|
||||
with {:ok, user} <- User.update_info(user, &User.Info.set_source_data(&1, source_data)) do
|
||||
user
|
||||
else
|
||||
_e ->
|
||||
user
|
||||
_e -> user
|
||||
end
|
||||
|
||||
ActivityPub.update(%{
|
||||
|
|
@ -336,34 +338,21 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
}
|
||||
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
true <- Visibility.is_public?(activity),
|
||||
%{valid?: true} = info_changeset <- User.Info.add_pinnned_activity(user.info, activity),
|
||||
changeset <-
|
||||
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
|
||||
{:ok, _user} <- User.update_and_set_cache(changeset) do
|
||||
{:ok, _user} <- User.update_info(user, &User.Info.add_pinnned_activity(&1, activity)) do
|
||||
{:ok, activity}
|
||||
else
|
||||
%{errors: [pinned_activities: {err, _}]} ->
|
||||
{:error, err}
|
||||
|
||||
_ ->
|
||||
{:error, dgettext("errors", "Could not pin")}
|
||||
{:error, %{changes: %{info: %{errors: [pinned_activities: {err, _}]}}}} -> {:error, err}
|
||||
_ -> {:error, dgettext("errors", "Could not pin")}
|
||||
end
|
||||
end
|
||||
|
||||
def unpin(id_or_ap_id, user) do
|
||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
%{valid?: true} = info_changeset <-
|
||||
User.Info.remove_pinnned_activity(user.info, activity),
|
||||
changeset <-
|
||||
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
|
||||
{:ok, _user} <- User.update_and_set_cache(changeset) do
|
||||
{:ok, _user} <- User.update_info(user, &User.Info.remove_pinnned_activity(&1, activity)) do
|
||||
{:ok, activity}
|
||||
else
|
||||
%{errors: [pinned_activities: {err, _}]} ->
|
||||
{:error, err}
|
||||
|
||||
_ ->
|
||||
{:error, dgettext("errors", "Could not unpin")}
|
||||
%{errors: [pinned_activities: {err, _}]} -> {:error, err}
|
||||
_ -> {:error, dgettext("errors", "Could not unpin")}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -458,23 +447,15 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
|
||||
defp set_visibility(activity, _), do: {:ok, activity}
|
||||
|
||||
def hide_reblogs(user, muted) do
|
||||
ap_id = muted.ap_id
|
||||
|
||||
def hide_reblogs(user, %{ap_id: ap_id} = _muted) do
|
||||
if ap_id not in user.info.muted_reblogs do
|
||||
info_changeset = User.Info.add_reblog_mute(user.info, ap_id)
|
||||
changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset)
|
||||
User.update_and_set_cache(changeset)
|
||||
User.update_info(user, &User.Info.add_reblog_mute(&1, ap_id))
|
||||
end
|
||||
end
|
||||
|
||||
def show_reblogs(user, muted) do
|
||||
ap_id = muted.ap_id
|
||||
|
||||
def show_reblogs(user, %{ap_id: ap_id} = _muted) do
|
||||
if ap_id in user.info.muted_reblogs do
|
||||
info_changeset = User.Info.remove_reblog_mute(user.info, ap_id)
|
||||
changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset)
|
||||
User.update_and_set_cache(changeset)
|
||||
User.update_info(user, &User.Info.remove_reblog_mute(&1, ap_id))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
alias Pleroma.Activity
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Emoji
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Plugs.AuthenticationPlug
|
||||
|
|
@ -25,7 +26,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
# This is a hack for twidere.
|
||||
def get_by_id_or_ap_id(id) do
|
||||
activity =
|
||||
with true <- Pleroma.FlakeId.is_flake_id?(id),
|
||||
with true <- FlakeId.flake_id?(id),
|
||||
%Activity{} = activity <- Activity.get_by_id_with_object(id) do
|
||||
activity
|
||||
else
|
||||
|
|
@ -184,7 +185,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
"name" => option,
|
||||
"type" => "Note",
|
||||
"replies" => %{"type" => "Collection", "totalItems" => 0}
|
||||
}, Map.merge(emoji, Formatter.get_emoji_map(option))}
|
||||
}, Map.merge(emoji, Emoji.Formatter.get_emoji_map(option))}
|
||||
end)
|
||||
|
||||
case expires_in do
|
||||
|
|
@ -434,8 +435,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
end
|
||||
|
||||
def emoji_from_profile(%{info: _info} = user) do
|
||||
(Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name))
|
||||
|> Enum.map(fn {shortcode, url, _} ->
|
||||
(Emoji.Formatter.get_emoji(user.bio) ++ Emoji.Formatter.get_emoji(user.name))
|
||||
|> Enum.map(fn {shortcode, %Emoji{file: url}} ->
|
||||
%{
|
||||
"type" => "Emoji",
|
||||
"icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"},
|
||||
|
|
|
|||
|
|
@ -13,10 +13,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
alias Pleroma.Bookmark
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Emoji
|
||||
alias Pleroma.Filter
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.HTTP
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Pagination
|
||||
alias Pleroma.Plugs.RateLimiter
|
||||
|
|
@ -35,7 +34,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
alias Pleroma.Web.MastodonAPI.ListView
|
||||
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
||||
alias Pleroma.Web.MastodonAPI.MastodonView
|
||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
||||
alias Pleroma.Web.MastodonAPI.ReportView
|
||||
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
|
@ -141,7 +139,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
user_info_emojis =
|
||||
user.info
|
||||
|> Map.get(:emoji, [])
|
||||
|> Enum.concat(Formatter.get_emoji_map(emojis_text))
|
||||
|> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text))
|
||||
|> Enum.dedup()
|
||||
|
||||
info_params =
|
||||
|
|
@ -154,7 +152,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
:hide_follows,
|
||||
:hide_favorites,
|
||||
:show_role,
|
||||
:skip_thread_containment
|
||||
:skip_thread_containment,
|
||||
:discoverable
|
||||
]
|
||||
|> Enum.reduce(%{}, fn key, acc ->
|
||||
add_if_present(acc, params, to_string(key), key, fn value ->
|
||||
|
|
@ -189,14 +188,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end)
|
||||
|> Map.put(:emoji, user_info_emojis)
|
||||
|
||||
info_cng = User.Info.profile_update(user.info, info_params)
|
||||
changeset =
|
||||
user
|
||||
|> User.update_changeset(user_params)
|
||||
|> User.change_info(&User.Info.profile_update(&1, info_params))
|
||||
|
||||
with changeset <- User.update_changeset(user, user_params),
|
||||
changeset <- Changeset.put_embed(changeset, :info, info_cng),
|
||||
{:ok, user} <- User.update_and_set_cache(changeset) do
|
||||
if original_user != user do
|
||||
CommonAPI.update(user)
|
||||
end
|
||||
with {:ok, user} <- User.update_and_set_cache(changeset) do
|
||||
if original_user != user, do: CommonAPI.update(user)
|
||||
|
||||
json(
|
||||
conn,
|
||||
|
|
@ -226,12 +224,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
|
||||
with new_info <- %{"banner" => %{}},
|
||||
info_cng <- User.Info.profile_update(user.info, new_info),
|
||||
changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng),
|
||||
{:ok, user} <- User.update_and_set_cache(changeset) do
|
||||
CommonAPI.update(user)
|
||||
new_info = %{"banner" => %{}}
|
||||
|
||||
with {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
|
||||
CommonAPI.update(user)
|
||||
json(conn, %{url: nil})
|
||||
end
|
||||
end
|
||||
|
|
@ -239,9 +235,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
def update_banner(%{assigns: %{user: user}} = conn, params) do
|
||||
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
|
||||
new_info <- %{"banner" => object.data},
|
||||
info_cng <- User.Info.profile_update(user.info, new_info),
|
||||
changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng),
|
||||
{:ok, user} <- User.update_and_set_cache(changeset) do
|
||||
{:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
|
||||
CommonAPI.update(user)
|
||||
%{"url" => [%{"href" => href} | _]} = object.data
|
||||
|
||||
|
|
@ -250,10 +244,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
|
||||
with new_info <- %{"background" => %{}},
|
||||
info_cng <- User.Info.profile_update(user.info, new_info),
|
||||
changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng),
|
||||
{:ok, _user} <- User.update_and_set_cache(changeset) do
|
||||
new_info = %{"background" => %{}}
|
||||
|
||||
with {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
|
||||
json(conn, %{url: nil})
|
||||
end
|
||||
end
|
||||
|
|
@ -261,9 +254,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
def update_background(%{assigns: %{user: user}} = conn, params) do
|
||||
with {:ok, object} <- ActivityPub.upload(params, type: :background),
|
||||
new_info <- %{"background" => object.data},
|
||||
info_cng <- User.Info.profile_update(user.info, new_info),
|
||||
changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng),
|
||||
{:ok, _user} <- User.update_and_set_cache(changeset) do
|
||||
{:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
|
||||
%{"url" => [%{"href" => href} | _]} = object.data
|
||||
|
||||
json(conn, %{url: href})
|
||||
|
|
@ -334,7 +325,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|
||||
defp mastodonized_emoji do
|
||||
Pleroma.Emoji.get_all()
|
||||
|> Enum.map(fn {shortcode, relative_url, tags} ->
|
||||
|> Enum.map(fn {shortcode, %Pleroma.Emoji{file: relative_url, tags: tags}} ->
|
||||
url = to_string(URI.merge(Web.base_url(), relative_url))
|
||||
|
||||
%{
|
||||
|
|
@ -721,49 +712,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def notifications(%{assigns: %{user: user}} = conn, params) do
|
||||
notifications = MastodonAPI.get_notifications(user, params)
|
||||
|
||||
conn
|
||||
|> add_link_headers(notifications)
|
||||
|> put_view(NotificationView)
|
||||
|> render("index.json", %{notifications: notifications, for: user})
|
||||
end
|
||||
|
||||
def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
|
||||
with {:ok, notification} <- Notification.get(user, id) do
|
||||
conn
|
||||
|> put_view(NotificationView)
|
||||
|> render("show.json", %{notification: notification, for: user})
|
||||
else
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{"error" => reason})
|
||||
end
|
||||
end
|
||||
|
||||
def clear_notifications(%{assigns: %{user: user}} = conn, _params) do
|
||||
Notification.clear(user)
|
||||
json(conn, %{})
|
||||
end
|
||||
|
||||
def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
|
||||
with {:ok, _notif} <- Notification.dismiss(user, id) do
|
||||
json(conn, %{})
|
||||
else
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{"error" => reason})
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do
|
||||
Notification.destroy_multiple(user, ids)
|
||||
json(conn, %{})
|
||||
end
|
||||
|
||||
def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
targets = User.get_all_by_ids(List.wrap(id))
|
||||
|
||||
|
|
@ -811,16 +759,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do
|
||||
with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)),
|
||||
%{} = attachment_data <- Map.put(object.data, "id", object.id),
|
||||
# Reject if not an image
|
||||
%{type: "image"} = rendered <-
|
||||
StatusView.render("attachment.json", %{attachment: attachment_data}),
|
||||
{:ok, _user} = User.update_mascot(user, rendered) do
|
||||
StatusView.render("attachment.json", %{attachment: attachment_data}) do
|
||||
# Sure!
|
||||
# Save to the user's info
|
||||
{:ok, _user} = User.update_info(user, &User.Info.mascot_update(&1, rendered))
|
||||
|
||||
json(conn, rendered)
|
||||
else
|
||||
%{type: _type} = _ ->
|
||||
render_error(conn, :unsupported_media_type, "mascots can only be images")
|
||||
|
||||
e ->
|
||||
e
|
||||
%{type: _} -> render_error(conn, :unsupported_media_type, "mascots can only be images")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -942,11 +890,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def follow_requests(%{assigns: %{user: followed}} = conn, _params) do
|
||||
with {:ok, follow_requests} <- User.get_follow_requests(followed) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("accounts.json", %{for: followed, users: follow_requests, as: :user})
|
||||
end
|
||||
follow_requests = User.get_follow_requests(followed)
|
||||
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("accounts.json", %{for: followed, users: follow_requests, as: :user})
|
||||
end
|
||||
|
||||
def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
|
||||
|
|
@ -1348,11 +1296,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
|
||||
info_cng = User.Info.mastodon_settings_update(user.info, settings)
|
||||
|
||||
with changeset <- Changeset.change(user),
|
||||
changeset <- Changeset.put_embed(changeset, :info, info_cng),
|
||||
{:ok, _user} <- User.update_and_set_cache(changeset) do
|
||||
with {:ok, _} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do
|
||||
json(conn, %{})
|
||||
else
|
||||
e ->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MastodonAPI.NotificationController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
||||
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
||||
|
||||
# GET /api/v1/notifications
|
||||
def index(%{assigns: %{user: user}} = conn, params) do
|
||||
notifications = MastodonAPI.get_notifications(user, params)
|
||||
|
||||
conn
|
||||
|> add_link_headers(notifications)
|
||||
|> render("index.json", notifications: notifications, for: user)
|
||||
end
|
||||
|
||||
# GET /api/v1/notifications/:id
|
||||
def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with {:ok, notification} <- Notification.get(user, id) do
|
||||
render(conn, "show.json", notification: notification, for: user)
|
||||
else
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{"error" => reason})
|
||||
end
|
||||
end
|
||||
|
||||
# POST /api/v1/notifications/clear
|
||||
def clear(%{assigns: %{user: user}} = conn, _params) do
|
||||
Notification.clear(user)
|
||||
json(conn, %{})
|
||||
end
|
||||
|
||||
# POST /api/v1/notifications/dismiss
|
||||
def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
|
||||
with {:ok, _notif} <- Notification.dismiss(user, id) do
|
||||
json(conn, %{})
|
||||
else
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{"error" => reason})
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /api/v1/notifications/destroy_multiple
|
||||
def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do
|
||||
Notification.destroy_multiple(user, ids)
|
||||
json(conn, %{})
|
||||
end
|
||||
end
|
||||
|
|
@ -116,6 +116,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for]))
|
||||
relationship = render("relationship.json", %{user: opts[:for], target: user})
|
||||
|
||||
discoverable = user.info.discoverable
|
||||
|
||||
%{
|
||||
id: to_string(user.id),
|
||||
username: username_from_nickname(user.nickname),
|
||||
|
|
@ -139,7 +141,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
note: HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
|
||||
sensitive: false,
|
||||
fields: raw_fields,
|
||||
pleroma: %{}
|
||||
pleroma: %{
|
||||
discoverable: discoverable
|
||||
}
|
||||
},
|
||||
|
||||
# Pleroma extension
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Metadata.Utils do
|
||||
alias Pleroma.Emoji
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.HTML
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
|
@ -13,7 +14,7 @@ defmodule Pleroma.Web.Metadata.Utils do
|
|||
|> HtmlEntities.decode()
|
||||
|> String.replace(~r/<br\s?\/?>/, " ")
|
||||
|> HTML.get_cached_stripped_html_for_activity(object, "metadata")
|
||||
|> Formatter.demojify()
|
||||
|> Emoji.Formatter.demojify()
|
||||
|> Formatter.truncate()
|
||||
end
|
||||
|
||||
|
|
@ -23,7 +24,7 @@ defmodule Pleroma.Web.Metadata.Utils do
|
|||
|> HtmlEntities.decode()
|
||||
|> String.replace(~r/<br\s?\/?>/, " ")
|
||||
|> HTML.strip_tags()
|
||||
|> Formatter.demojify()
|
||||
|> Emoji.Formatter.demojify()
|
||||
|> Formatter.truncate(max_length)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
|||
field(:scopes, {:array, :string}, default: [])
|
||||
field(:valid_until, :naive_datetime_usec)
|
||||
field(:used, :boolean, default: false)
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
belongs_to(:app, App)
|
||||
|
||||
timestamps()
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ defmodule Pleroma.Web.OAuth.Token do
|
|||
field(:refresh_token, :string)
|
||||
field(:scopes, {:array, :string}, default: [])
|
||||
field(:valid_until, :naive_datetime_usec)
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
belongs_to(:app, App)
|
||||
|
||||
timestamps()
|
||||
|
|
|
|||
|
|
@ -3,12 +3,33 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
|
|||
|
||||
require Logger
|
||||
|
||||
@emoji_dir_path Path.join(
|
||||
Pleroma.Config.get!([:instance, :static_dir]),
|
||||
"emoji"
|
||||
)
|
||||
def emoji_dir_path do
|
||||
Path.join(
|
||||
Pleroma.Config.get!([:instance, :static_dir]),
|
||||
"emoji"
|
||||
)
|
||||
end
|
||||
|
||||
@cache_seconds_per_file Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file])
|
||||
@doc """
|
||||
Lists packs from the remote instance.
|
||||
|
||||
Since JS cannot ask remote instances for their packs due to CPS, it has to
|
||||
be done by the server
|
||||
"""
|
||||
def list_from(conn, %{"instance_address" => address}) do
|
||||
address = String.trim(address)
|
||||
|
||||
if shareable_packs_available(address) do
|
||||
list_resp =
|
||||
"#{address}/api/pleroma/emoji/packs" |> Tesla.get!() |> Map.get(:body) |> Jason.decode!()
|
||||
|
||||
json(conn, list_resp)
|
||||
else
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "The requested instance does not support sharing emoji packs"})
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Lists the packs available on the instance as JSON.
|
||||
|
|
@ -17,7 +38,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
|
|||
a map of "pack directory name" to pack.json contents.
|
||||
"""
|
||||
def list_packs(conn, _params) do
|
||||
with {:ok, results} <- File.ls(@emoji_dir_path) do
|
||||
# Create the directory first if it does not exist. This is probably the first request made
|
||||
# with the API so it should be sufficient
|
||||
with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_dir_path())},
|
||||
{:ls, {:ok, results}} <- {:ls, File.ls(emoji_dir_path())} do
|
||||
pack_infos =
|
||||
results
|
||||
|> Enum.filter(&has_pack_json?/1)
|
||||
|
|
@ -28,24 +52,37 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
|
|||
|> Enum.into(%{})
|
||||
|
||||
json(conn, pack_infos)
|
||||
else
|
||||
{:create_dir, {:error, e}} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "Failed to create the emoji pack directory at #{emoji_dir_path()}: #{e}"})
|
||||
|
||||
{:ls, {:error, e}} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{
|
||||
error:
|
||||
"Failed to get the contents of the emoji pack directory at #{emoji_dir_path()}: #{e}"
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
defp has_pack_json?(file) do
|
||||
dir_path = Path.join(@emoji_dir_path, file)
|
||||
dir_path = Path.join(emoji_dir_path(), file)
|
||||
# Filter to only use the pack.json packs
|
||||
File.dir?(dir_path) and File.exists?(Path.join(dir_path, "pack.json"))
|
||||
end
|
||||
|
||||
defp load_pack(pack_name) do
|
||||
pack_path = Path.join(@emoji_dir_path, pack_name)
|
||||
pack_path = Path.join(emoji_dir_path(), pack_name)
|
||||
pack_file = Path.join(pack_path, "pack.json")
|
||||
|
||||
{pack_name, Jason.decode!(File.read!(pack_file))}
|
||||
end
|
||||
|
||||
defp validate_pack({name, pack}) do
|
||||
pack_path = Path.join(@emoji_dir_path, name)
|
||||
pack_path = Path.join(emoji_dir_path(), name)
|
||||
|
||||
if can_download?(pack, pack_path) do
|
||||
archive_for_sha = make_archive(name, pack, pack_path)
|
||||
|
|
@ -79,7 +116,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
|
|||
|
||||
{:ok, {_, zip_result}} = :zip.zip('#{name}.zip', files, [:memory, cwd: to_charlist(pack_dir)])
|
||||
|
||||
cache_ms = :timer.seconds(@cache_seconds_per_file * Enum.count(files))
|
||||
cache_seconds_per_file = Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file])
|
||||
cache_ms = :timer.seconds(cache_seconds_per_file * Enum.count(files))
|
||||
|
||||
Cachex.put!(
|
||||
:emoji_packs_cache,
|
||||
|
|
@ -115,7 +153,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
|
|||
to download packs that the instance shares.
|
||||
"""
|
||||
def download_shared(conn, %{"name" => name}) do
|
||||
pack_dir = Path.join(@emoji_dir_path, name)
|
||||
pack_dir = Path.join(emoji_dir_path(), name)
|
||||
pack_file = Path.join(pack_dir, "pack.json")
|
||||
|
||||
with {_, true} <- {:exists?, File.exists?(pack_file)},
|
||||
|
|
@ -139,6 +177,22 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
|
|||
end
|
||||
end
|
||||
|
||||
defp shareable_packs_available(address) do
|
||||
"#{address}/.well-known/nodeinfo"
|
||||
|> Tesla.get!()
|
||||
|> Map.get(:body)
|
||||
|> Jason.decode!()
|
||||
|> Map.get("links")
|
||||
|> List.last()
|
||||
|> Map.get("href")
|
||||
# Get the actual nodeinfo address and fetch it
|
||||
|> Tesla.get!()
|
||||
|> Map.get(:body)
|
||||
|> Jason.decode!()
|
||||
|> get_in(["metadata", "features"])
|
||||
|> Enum.member?("shareable_emoji_packs")
|
||||
end
|
||||
|
||||
@doc """
|
||||
An admin endpoint to request downloading a pack named `pack_name` from the instance
|
||||
`instance_address`.
|
||||
|
|
@ -147,21 +201,9 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
|
|||
from that instance, otherwise it will be downloaded from the fallback source, if there is one.
|
||||
"""
|
||||
def download_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do
|
||||
shareable_packs_available =
|
||||
"#{address}/.well-known/nodeinfo"
|
||||
|> Tesla.get!()
|
||||
|> Map.get(:body)
|
||||
|> Jason.decode!()
|
||||
|> List.last()
|
||||
|> Map.get("href")
|
||||
# Get the actual nodeinfo address and fetch it
|
||||
|> Tesla.get!()
|
||||
|> Map.get(:body)
|
||||
|> Jason.decode!()
|
||||
|> get_in(["metadata", "features"])
|
||||
|> Enum.member?("shareable_emoji_packs")
|
||||
address = String.trim(address)
|
||||
|
||||
if shareable_packs_available do
|
||||
if shareable_packs_available(address) do
|
||||
full_pack =
|
||||
"#{address}/api/pleroma/emoji/packs/list"
|
||||
|> Tesla.get!()
|
||||
|
|
@ -195,7 +237,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
|
|||
%{body: emoji_archive} <- Tesla.get!(uri),
|
||||
{_, true} <- {:checksum, Base.decode16!(sha) == :crypto.hash(:sha256, emoji_archive)} do
|
||||
local_name = data["as"] || name
|
||||
pack_dir = Path.join(@emoji_dir_path, local_name)
|
||||
pack_dir = Path.join(emoji_dir_path(), local_name)
|
||||
File.mkdir_p!(pack_dir)
|
||||
|
||||
files = Enum.map(full_pack["files"], fn {_, path} -> to_charlist(path) end)
|
||||
|
|
@ -233,7 +275,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
|
|||
Creates an empty pack named `name` which then can be updated via the admin UI.
|
||||
"""
|
||||
def create(conn, %{"name" => name}) do
|
||||
pack_dir = Path.join(@emoji_dir_path, name)
|
||||
pack_dir = Path.join(emoji_dir_path(), name)
|
||||
|
||||
if not File.exists?(pack_dir) do
|
||||
File.mkdir_p!(pack_dir)
|
||||
|
|
@ -257,7 +299,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
|
|||
Deletes the pack `name` and all it's files.
|
||||
"""
|
||||
def delete(conn, %{"name" => name}) do
|
||||
pack_dir = Path.join(@emoji_dir_path, name)
|
||||
pack_dir = Path.join(emoji_dir_path(), name)
|
||||
|
||||
case File.rm_rf(pack_dir) do
|
||||
{:ok, _} ->
|
||||
|
|
@ -276,7 +318,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
|
|||
`new_data` is the new metadata for the pack, that will replace the old metadata.
|
||||
"""
|
||||
def update_metadata(conn, %{"pack_name" => name, "new_data" => new_data}) do
|
||||
pack_file_p = Path.join([@emoji_dir_path, name, "pack.json"])
|
||||
pack_file_p = Path.join([emoji_dir_path(), name, "pack.json"])
|
||||
|
||||
full_pack = Jason.decode!(File.read!(pack_file_p))
|
||||
|
||||
|
|
@ -360,7 +402,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
|
|||
conn,
|
||||
%{"pack_name" => pack_name, "action" => "add", "shortcode" => shortcode} = params
|
||||
) do
|
||||
pack_dir = Path.join(@emoji_dir_path, pack_name)
|
||||
pack_dir = Path.join(emoji_dir_path(), pack_name)
|
||||
pack_file_p = Path.join(pack_dir, "pack.json")
|
||||
|
||||
full_pack = Jason.decode!(File.read!(pack_file_p))
|
||||
|
|
@ -408,7 +450,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
|
|||
"action" => "remove",
|
||||
"shortcode" => shortcode
|
||||
}) do
|
||||
pack_dir = Path.join(@emoji_dir_path, pack_name)
|
||||
pack_dir = Path.join(emoji_dir_path(), pack_name)
|
||||
pack_file_p = Path.join(pack_dir, "pack.json")
|
||||
|
||||
full_pack = Jason.decode!(File.read!(pack_file_p))
|
||||
|
|
@ -443,7 +485,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
|
|||
conn,
|
||||
%{"pack_name" => pack_name, "action" => "update", "shortcode" => shortcode} = params
|
||||
) do
|
||||
pack_dir = Path.join(@emoji_dir_path, pack_name)
|
||||
pack_dir = Path.join(emoji_dir_path(), pack_name)
|
||||
pack_file_p = Path.join(pack_dir, "pack.json")
|
||||
|
||||
full_pack = Jason.decode!(File.read!(pack_file_p))
|
||||
|
|
@ -513,11 +555,11 @@ 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
|
||||
with {:ok, results} <- File.ls(emoji_dir_path()) do
|
||||
imported_pack_names =
|
||||
results
|
||||
|> Enum.filter(fn file ->
|
||||
dir_path = Path.join(@emoji_dir_path, file)
|
||||
dir_path = Path.join(emoji_dir_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)
|
||||
|
|
@ -533,7 +575,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
|
|||
end
|
||||
|
||||
defp write_pack_json_contents(dir) do
|
||||
dir_path = Path.join(@emoji_dir_path, dir)
|
||||
dir_path = Path.join(emoji_dir_path(), dir)
|
||||
emoji_txt_path = Path.join(dir_path, "emoji.txt")
|
||||
|
||||
files_for_pack = files_for_pack(emoji_txt_path, dir_path)
|
||||
|
|
@ -569,7 +611,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
|
|||
# If there's no emoji.txt, assume all files
|
||||
# that are of certain extensions from the config are emojis and import them all
|
||||
pack_extensions = Pleroma.Config.get!([:emoji, :pack_extensions])
|
||||
Pleroma.Emoji.make_shortcode_to_file_map(dir_path, pack_extensions)
|
||||
Pleroma.Emoji.Loader.make_shortcode_to_file_map(dir_path, pack_extensions)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ defmodule Pleroma.Web.Push.Subscription do
|
|||
@type t :: %__MODULE__{}
|
||||
|
||||
schema "push_subscriptions" do
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
belongs_to(:token, Token)
|
||||
field(:endpoint, :string)
|
||||
field(:key_p256dh, :string)
|
||||
|
|
|
|||
|
|
@ -222,6 +222,7 @@ defmodule Pleroma.Web.Router do
|
|||
put("/:name", EmojiAPIController, :create)
|
||||
delete("/:name", EmojiAPIController, :delete)
|
||||
post("/download_from", EmojiAPIController, :download_from)
|
||||
post("/list_from", EmojiAPIController, :list_from)
|
||||
end
|
||||
|
||||
scope "/packs" do
|
||||
|
|
@ -324,11 +325,11 @@ defmodule Pleroma.Web.Router do
|
|||
get("/favourites", MastodonAPIController, :favourites)
|
||||
get("/bookmarks", MastodonAPIController, :bookmarks)
|
||||
|
||||
post("/notifications/clear", MastodonAPIController, :clear_notifications)
|
||||
post("/notifications/dismiss", MastodonAPIController, :dismiss_notification)
|
||||
get("/notifications", MastodonAPIController, :notifications)
|
||||
get("/notifications/:id", MastodonAPIController, :get_notification)
|
||||
delete("/notifications/destroy_multiple", MastodonAPIController, :destroy_multiple)
|
||||
get("/notifications", NotificationController, :index)
|
||||
get("/notifications/:id", NotificationController, :show)
|
||||
post("/notifications/clear", NotificationController, :clear)
|
||||
post("/notifications/dismiss", NotificationController, :dismiss)
|
||||
delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
|
||||
|
||||
get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses)
|
||||
get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status)
|
||||
|
|
|
|||
|
|
@ -239,11 +239,9 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
|
||||
def emoji(conn, _params) do
|
||||
emoji =
|
||||
Emoji.get_all()
|
||||
|> Enum.map(fn {short_code, path, tags} ->
|
||||
{short_code, %{image_url: path, tags: tags}}
|
||||
Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc ->
|
||||
Map.put(acc, code, %{image_url: file, tags: tags})
|
||||
end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
json(conn, emoji)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
defmodule Pleroma.Web.TwitterAPI.Controller do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Ecto.Changeset
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
|
@ -16,15 +15,12 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
action_fallback(:errors)
|
||||
|
||||
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
|
||||
with %User{} = user <- User.get_cached_by_id(uid),
|
||||
true <- user.local,
|
||||
true <- user.info.confirmation_pending,
|
||||
true <- user.info.confirmation_token == token,
|
||||
info_change <- User.Info.confirmation_changeset(user.info, need_confirmation: false),
|
||||
changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_change),
|
||||
{:ok, _} <- User.update_and_set_cache(changeset) do
|
||||
conn
|
||||
|> redirect(to: "/")
|
||||
new_info = [need_confirmation: false]
|
||||
|
||||
with %User{info: info} = user <- User.get_cached_by_id(uid),
|
||||
true <- user.local and info.confirmation_pending and info.confirmation_token == token,
|
||||
{:ok, _} <- User.update_info(user, &User.Info.confirmation_changeset(&1, new_info)) do
|
||||
redirect(conn, to: "/")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ defmodule Pleroma.Web.Websub.WebsubClientSubscription do
|
|||
field(:state, :string)
|
||||
field(:subscribers, {:array, :string}, default: [])
|
||||
field(:hub, :string)
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue