Merge branch 'develop' of https://akkoma.dev/AkkomaGang/akkoma into froth-akkoma
This commit is contained in:
commit
2172cb0e38
20 changed files with 381 additions and 15 deletions
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -4,10 +4,22 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## Unreleased
|
||||
## 2023.05
|
||||
|
||||
## Added
|
||||
- Custom options for users to accept/reject private messages
|
||||
- options: everybody, nobody, people\_i\_follow
|
||||
- MRF to reject notes from accounts newer than a given age
|
||||
- this will have the side-effect of rejecting legitimate messages if your
|
||||
post gets boosted outside of your local bubble and people your instance
|
||||
does not know about reply to it.
|
||||
|
||||
## Fixed
|
||||
- Support for `streams` public key URIs
|
||||
- Bookmarks are cleaned up on DB prune now
|
||||
|
||||
## Security
|
||||
- Fixed mediaproxy being a bit of a silly billy
|
||||
|
||||
## 2023.04
|
||||
|
||||
|
|
|
@ -429,6 +429,8 @@
|
|||
|
||||
config :pleroma, :mrf_follow_bot, follower_nickname: nil
|
||||
|
||||
config :pleroma, :mrf_reject_newly_created_account_notes, age: 86_400
|
||||
|
||||
config :pleroma, :rich_media,
|
||||
enabled: true,
|
||||
ignore_hosts: [],
|
||||
|
|
|
@ -39,9 +39,9 @@ def translate(string, from_language, to_language) do
|
|||
detected =
|
||||
if Map.has_key?(body, "detectedLanguage") do
|
||||
get_in(body, ["detectedLanguage", "language"])
|
||||
else
|
||||
else
|
||||
from_language || ""
|
||||
end
|
||||
end
|
||||
|
||||
{:ok, detected, translated}
|
||||
else
|
||||
|
|
|
@ -251,6 +251,7 @@ defp build_resp_headers(headers, opts) do
|
|||
|> Enum.filter(fn {k, _} -> k in @keep_resp_headers end)
|
||||
|> build_resp_cache_headers(opts)
|
||||
|> build_resp_content_disposition_header(opts)
|
||||
|> build_csp_headers()
|
||||
|> Keyword.merge(Keyword.get(opts, :resp_headers, []))
|
||||
end
|
||||
|
||||
|
@ -316,6 +317,10 @@ defp build_resp_content_disposition_header(headers, opts) do
|
|||
end
|
||||
end
|
||||
|
||||
defp build_csp_headers(headers) do
|
||||
List.keystore(headers, "content-security-policy", 0, {"content-security-policy", "sandbox"})
|
||||
end
|
||||
|
||||
defp header_length_constraint(headers, limit) when is_integer(limit) and limit > 0 do
|
||||
with {_, size} <- List.keyfind(headers, "content-length", 0),
|
||||
{size, _} <- Integer.parse(size),
|
||||
|
|
|
@ -160,6 +160,11 @@ defmodule Pleroma.User do
|
|||
field(:language, :string)
|
||||
field(:status_ttl_days, :integer, default: nil)
|
||||
|
||||
field(:accepts_direct_messages_from, Ecto.Enum,
|
||||
values: [:everybody, :people_i_follow, :nobody],
|
||||
default: :everybody
|
||||
)
|
||||
|
||||
embeds_one(
|
||||
:notification_settings,
|
||||
Pleroma.User.NotificationSetting,
|
||||
|
@ -550,7 +555,8 @@ def update_changeset(struct, params \\ %{}) do
|
|||
:actor_type,
|
||||
:accepts_chat_messages,
|
||||
:disclose_client,
|
||||
:status_ttl_days
|
||||
:status_ttl_days,
|
||||
:accepts_direct_messages_from
|
||||
]
|
||||
)
|
||||
|> unique_constraint(:nickname)
|
||||
|
@ -2748,4 +2754,16 @@ def unfollow_hashtag(%User{} = user, %Hashtag{} = hashtag) do
|
|||
def following_hashtag?(%User{} = user, %Hashtag{} = hashtag) do
|
||||
not is_nil(HashtagFollow.get(user, hashtag))
|
||||
end
|
||||
|
||||
def accepts_direct_messages?(
|
||||
%User{accepts_direct_messages_from: :people_i_follow} = receiver,
|
||||
%User{} = sender
|
||||
) do
|
||||
User.following?(receiver, sender)
|
||||
end
|
||||
|
||||
def accepts_direct_messages?(%User{accepts_direct_messages_from: :everybody}, _), do: true
|
||||
|
||||
def accepts_direct_messages?(%User{accepts_direct_messages_from: :nobody}, _),
|
||||
do: false
|
||||
end
|
||||
|
|
|
@ -141,7 +141,8 @@ def get_policies do
|
|||
|> Enum.concat([
|
||||
Pleroma.Web.ActivityPub.MRF.HashtagPolicy,
|
||||
Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy,
|
||||
Pleroma.Web.ActivityPub.MRF.NormalizeMarkup
|
||||
Pleroma.Web.ActivityPub.MRF.NormalizeMarkup,
|
||||
Pleroma.Web.ActivityPub.MRF.DirectMessageDisabledPolicy
|
||||
])
|
||||
|> Enum.uniq()
|
||||
end
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
defmodule Pleroma.Web.ActivityPub.MRF.DirectMessageDisabledPolicy do
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||
|
||||
alias Pleroma.User
|
||||
require Pleroma.Constants
|
||||
|
||||
@moduledoc """
|
||||
Removes entries from the "To" field from direct messages if the user has requested to not
|
||||
allow direct messages
|
||||
"""
|
||||
|
||||
@impl true
|
||||
def filter(
|
||||
%{
|
||||
"type" => "Create",
|
||||
"actor" => actor,
|
||||
"object" => %{
|
||||
"type" => "Note"
|
||||
}
|
||||
} = activity
|
||||
) do
|
||||
with recipients <- Map.get(activity, "to", []),
|
||||
cc <- Map.get(activity, "cc", []),
|
||||
true <- is_direct?(recipients, cc),
|
||||
sender <- User.get_cached_by_ap_id(actor) do
|
||||
new_to =
|
||||
Enum.filter(recipients, fn recv ->
|
||||
should_include?(sender, recv)
|
||||
end)
|
||||
|
||||
{:ok,
|
||||
activity
|
||||
|> Map.put("to", new_to)
|
||||
|> maybe_replace_object_to(new_to)}
|
||||
else
|
||||
_ ->
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def filter(object), do: {:ok, object}
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
|
||||
defp should_include?(sender, receiver_ap_id) do
|
||||
with %User{local: true} = receiver <- User.get_cached_by_ap_id(receiver_ap_id) do
|
||||
User.accepts_direct_messages?(receiver, sender)
|
||||
else
|
||||
_ -> true
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_replace_object_to(%{"object" => %{"to" => _}} = activity, to) do
|
||||
Kernel.put_in(activity, ["object", "to"], to)
|
||||
end
|
||||
|
||||
defp maybe_replace_object_to(other, _), do: other
|
||||
|
||||
defp is_direct?(to, cc) do
|
||||
!(Enum.member?(to, Pleroma.Constants.as_public()) ||
|
||||
Enum.member?(cc, Pleroma.Constants.as_public()))
|
||||
end
|
||||
end
|
|
@ -0,0 +1,50 @@
|
|||
defmodule Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy do
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||
|
||||
alias Pleroma.User
|
||||
|
||||
@moduledoc """
|
||||
Rejects notes from accounts that were created below a certain threshold of time ago
|
||||
"""
|
||||
@impl true
|
||||
def filter(
|
||||
%{
|
||||
"type" => type,
|
||||
"actor" => actor
|
||||
} = activity
|
||||
)
|
||||
when type in ["Note", "Create"] do
|
||||
min_age = Pleroma.Config.get([:mrf_reject_newly_created_account_notes, :age])
|
||||
|
||||
with %User{local: false} = user <- Pleroma.User.get_cached_by_ap_id(actor),
|
||||
true <- Timex.diff(Timex.now(), user.inserted_at, :seconds) < min_age do
|
||||
{:reject, "[RejectNewlyCreatedAccountNotesPolicy] Account created too recently"}
|
||||
else
|
||||
_ -> {:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def filter(object), do: {:ok, object}
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
|
||||
@impl true
|
||||
def config_description do
|
||||
%{
|
||||
key: :mrf_reject_newly_created_account_notes,
|
||||
related_policy: "Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy",
|
||||
label: "MRF Reject New Accounts",
|
||||
description: "Reject notes from accounts created too recently",
|
||||
children: [
|
||||
%{
|
||||
key: :age,
|
||||
type: :integer,
|
||||
description: "Time below which to reject (in seconds)",
|
||||
suggestions: [86_400]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
|
@ -713,6 +713,16 @@ defp update_credentials_request do
|
|||
nullable: true,
|
||||
description:
|
||||
"Number of days after which statuses will be deleted. Set to -1 to disable."
|
||||
},
|
||||
accepts_direct_messages_from: %Schema{
|
||||
type: :string,
|
||||
enum: [
|
||||
"everybody",
|
||||
"nobody",
|
||||
"people_i_follow"
|
||||
],
|
||||
nullable: true,
|
||||
description: "Who to accept DMs from"
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
|
@ -734,7 +744,8 @@ defp update_credentials_request do
|
|||
also_known_as: ["https://foo.bar/users/foo"],
|
||||
discoverable: false,
|
||||
actor_type: "Person",
|
||||
status_ttl_days: 30
|
||||
status_ttl_days: 30,
|
||||
accepts_direct_messages_from: "everybody"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
@ -225,6 +225,7 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
|
|||
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
|
||||
|> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language]))
|
||||
|> Maps.put_if_present(:status_ttl_days, params[:status_ttl_days], status_ttl_days_value)
|
||||
|> Maps.put_if_present(:accepts_direct_messages_from, params[:accepts_direct_messages_from])
|
||||
|
||||
IO.inspect(user_params)
|
||||
# What happens here:
|
||||
|
|
|
@ -356,6 +356,7 @@ defp maybe_put_settings(
|
|||
|> Kernel.put_in([:source, :privacy], user.default_scope)
|
||||
|> Kernel.put_in([:source, :pleroma, :show_role], user.show_role)
|
||||
|> Kernel.put_in([:source, :pleroma, :no_rich_text], user.no_rich_text)
|
||||
|> Kernel.put_in([:accepts_direct_messages_from], user.accepts_direct_messages_from)
|
||||
end
|
||||
|
||||
defp maybe_put_settings(data, _, _, _), do: data
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
|
|||
def project do
|
||||
[
|
||||
app: :pleroma,
|
||||
version: version("3.8.0"),
|
||||
version: version("3.9.2"),
|
||||
elixir: "~> 1.14",
|
||||
elixirc_paths: elixirc_paths(Mix.env()),
|
||||
compilers: [:phoenix] ++ Mix.compilers(),
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddUnfollowedDmRestrictions do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:users) do
|
||||
add(:accepts_direct_messages_from, :string, default: "everybody")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -146,8 +146,7 @@ test "should work when no detected language is received" do
|
|||
}
|
||||
end)
|
||||
|
||||
assert {:ok, "", "I will crush you"} =
|
||||
LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", nil, "en")
|
||||
assert {:ok, "", "I will crush you"} = LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", nil, "en")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2799,4 +2799,35 @@ test "should not error when trying to unfollow a hashtag twice" do
|
|||
assert user.followed_hashtags |> Enum.count() == 0
|
||||
end
|
||||
end
|
||||
|
||||
describe "accepts_direct_messages?/2" do
|
||||
test "should return true if the recipient follows the sender and has set accept to :people_i_follow" do
|
||||
recipient =
|
||||
insert(:user, %{
|
||||
accepts_direct_messages_from: :people_i_follow
|
||||
})
|
||||
|
||||
sender = insert(:user)
|
||||
|
||||
refute User.accepts_direct_messages?(recipient, sender)
|
||||
|
||||
CommonAPI.follow(recipient, sender)
|
||||
|
||||
assert User.accepts_direct_messages?(recipient, sender)
|
||||
end
|
||||
|
||||
test "should return true if the recipient has set accept to :everyone" do
|
||||
recipient = insert(:user, %{accepts_direct_messages_from: :everybody})
|
||||
sender = insert(:user)
|
||||
|
||||
assert User.accepts_direct_messages?(recipient, sender)
|
||||
end
|
||||
|
||||
test "should return false if the receipient set accept to :nobody" do
|
||||
recipient = insert(:user, %{accepts_direct_messages_from: :nobody})
|
||||
sender = insert(:user)
|
||||
|
||||
refute User.accepts_direct_messages?(recipient, sender)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
defmodule Pleroma.Web.ActivityPub.MRF.DirectMessageDisabledPolicyTest do
|
||||
use Pleroma.DataCase
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.Web.ActivityPub.MRF.DirectMessageDisabledPolicy
|
||||
alias Pleroma.User
|
||||
|
||||
describe "strips recipients" do
|
||||
test "when the user denies the direct message" do
|
||||
sender = insert(:user)
|
||||
recipient = insert(:user, %{accepts_direct_messages_from: :nobody})
|
||||
|
||||
refute User.accepts_direct_messages?(recipient, sender)
|
||||
|
||||
message = %{
|
||||
"actor" => sender.ap_id,
|
||||
"to" => [recipient.ap_id],
|
||||
"cc" => [],
|
||||
"type" => "Create",
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"to" => [recipient.ap_id]
|
||||
}
|
||||
}
|
||||
|
||||
assert {:ok, %{"to" => [], "object" => %{"to" => []}}} =
|
||||
DirectMessageDisabledPolicy.filter(message)
|
||||
end
|
||||
|
||||
test "when the user does not deny the direct message" do
|
||||
sender = insert(:user)
|
||||
recipient = insert(:user, %{accepts_direct_messages_from: :everybody})
|
||||
|
||||
assert User.accepts_direct_messages?(recipient, sender)
|
||||
|
||||
message = %{
|
||||
"actor" => sender.ap_id,
|
||||
"to" => [recipient.ap_id],
|
||||
"cc" => [],
|
||||
"type" => "Create",
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"to" => [recipient.ap_id]
|
||||
}
|
||||
}
|
||||
|
||||
assert {:ok, message} = DirectMessageDisabledPolicy.filter(message)
|
||||
assert message["to"] == [recipient.ap_id]
|
||||
assert message["object"]["to"] == [recipient.ap_id]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
defmodule Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicyTest do
|
||||
use Pleroma.DataCase
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy
|
||||
|
||||
describe "reject notes from new accounts" do
|
||||
test "rejects notes from accounts created more recently than `age`" do
|
||||
clear_config([:mrf_reject_newly_created_account_notes, :age], 86_400)
|
||||
sender = insert(:user, %{inserted_at: Timex.now(), local: false})
|
||||
|
||||
message = %{
|
||||
"actor" => sender.ap_id,
|
||||
"type" => "Create"
|
||||
}
|
||||
|
||||
assert {:reject, _} = RejectNewlyCreatedAccountNotesPolicy.filter(message)
|
||||
end
|
||||
|
||||
test "does not reject notes from accounts created longer ago" do
|
||||
clear_config([:mrf_reject_newly_created_account_notes, :age], 86_400)
|
||||
a_day_ago = Timex.shift(Timex.now(), days: -1)
|
||||
sender = insert(:user, %{inserted_at: a_day_ago, local: false})
|
||||
|
||||
message = %{
|
||||
"actor" => sender.ap_id,
|
||||
"type" => "Create"
|
||||
}
|
||||
|
||||
assert {:ok, _} = RejectNewlyCreatedAccountNotesPolicy.filter(message)
|
||||
end
|
||||
|
||||
test "does not affect local users" do
|
||||
clear_config([:mrf_reject_newly_created_account_notes, :age], 86_400)
|
||||
sender = insert(:user, %{inserted_at: Timex.now(), local: true})
|
||||
|
||||
message = %{
|
||||
"actor" => sender.ap_id,
|
||||
"type" => "Create"
|
||||
}
|
||||
|
||||
assert {:ok, _} = RejectNewlyCreatedAccountNotesPolicy.filter(message)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -102,7 +102,13 @@ test "it works as expected with noop policy" do
|
|||
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.NoOpPolicy])
|
||||
|
||||
expected = %{
|
||||
mrf_policies: ["NoOpPolicy", "HashtagPolicy", "InlineQuotePolicy", "NormalizeMarkup"],
|
||||
mrf_policies: [
|
||||
"NoOpPolicy",
|
||||
"HashtagPolicy",
|
||||
"InlineQuotePolicy",
|
||||
"NormalizeMarkup",
|
||||
"DirectMessageDisabledPolicy"
|
||||
],
|
||||
mrf_hashtag: %{
|
||||
federated_timeline_removal: [],
|
||||
reject: [],
|
||||
|
@ -118,7 +124,13 @@ test "it works as expected with mock policy" do
|
|||
clear_config([:mrf, :policies], [MRFModuleMock])
|
||||
|
||||
expected = %{
|
||||
mrf_policies: ["MRFModuleMock", "HashtagPolicy", "InlineQuotePolicy", "NormalizeMarkup"],
|
||||
mrf_policies: [
|
||||
"MRFModuleMock",
|
||||
"HashtagPolicy",
|
||||
"InlineQuotePolicy",
|
||||
"NormalizeMarkup",
|
||||
"DirectMessageDisabledPolicy"
|
||||
],
|
||||
mrf_module_mock: "some config data",
|
||||
mrf_hashtag: %{
|
||||
federated_timeline_removal: [],
|
||||
|
|
|
@ -316,7 +316,7 @@ test "it strips internal reactions" do
|
|||
test "it correctly processes messages with non-array to field" do
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||
|> Poison.decode!()
|
||||
|> Jason.decode!()
|
||||
|> Map.put("to", "https://www.w3.org/ns/activitystreams#Public")
|
||||
|> put_in(["object", "to"], "https://www.w3.org/ns/activitystreams#Public")
|
||||
|
||||
|
@ -333,7 +333,7 @@ test "it correctly processes messages with non-array to field" do
|
|||
test "it correctly processes messages with non-array cc field" do
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||
|> Poison.decode!()
|
||||
|> Jason.decode!()
|
||||
|> Map.put("cc", "http://mastodon.example.org/users/admin/followers")
|
||||
|> put_in(["object", "cc"], "http://mastodon.example.org/users/admin/followers")
|
||||
|
||||
|
@ -346,7 +346,7 @@ test "it correctly processes messages with non-array cc field" do
|
|||
test "it correctly processes messages with weirdness in address fields" do
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||
|> Poison.decode!()
|
||||
|> Jason.decode!()
|
||||
|> Map.put("cc", ["http://mastodon.example.org/users/admin/followers", ["¿"]])
|
||||
|> put_in(["object", "cc"], ["http://mastodon.example.org/users/admin/followers", ["¿"]])
|
||||
|
||||
|
@ -412,7 +412,7 @@ test "does NOT schedule background fetching of `replies` beyond max thread depth
|
|||
|
||||
activity =
|
||||
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||
|> Poison.decode!()
|
||||
|> Jason.decode!()
|
||||
|> Kernel.put_in(["object", "replies"], replies)
|
||||
|
||||
%{activity: activity}
|
||||
|
|
|
@ -734,4 +734,56 @@ test "actor_type field has a higher priority than bot", %{conn: conn} do
|
|||
assert account["source"]["pleroma"]["actor_type"] == "Person"
|
||||
end
|
||||
end
|
||||
|
||||
describe "Updating direct message settings" do
|
||||
setup do: oauth_access(["write:accounts"])
|
||||
setup :request_content_type
|
||||
|
||||
test "changing to :everybody", %{conn: conn} do
|
||||
account =
|
||||
conn
|
||||
|> patch("/api/v1/accounts/update_credentials", %{
|
||||
accepts_direct_messages_from: "everybody"
|
||||
})
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert account["accepts_direct_messages_from"]
|
||||
assert account["accepts_direct_messages_from"] == "everybody"
|
||||
assert Pleroma.User.get_by_ap_id(account["url"]).accepts_direct_messages_from == :everybody
|
||||
end
|
||||
|
||||
test "changing to :nobody", %{conn: conn} do
|
||||
account =
|
||||
conn
|
||||
|> patch("/api/v1/accounts/update_credentials", %{accepts_direct_messages_from: "nobody"})
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert account["accepts_direct_messages_from"]
|
||||
assert account["accepts_direct_messages_from"] == "nobody"
|
||||
assert Pleroma.User.get_by_ap_id(account["url"]).accepts_direct_messages_from == :nobody
|
||||
end
|
||||
|
||||
test "changing to :people_i_follow", %{conn: conn} do
|
||||
account =
|
||||
conn
|
||||
|> patch("/api/v1/accounts/update_credentials", %{
|
||||
accepts_direct_messages_from: "people_i_follow"
|
||||
})
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert account["accepts_direct_messages_from"]
|
||||
assert account["accepts_direct_messages_from"] == "people_i_follow"
|
||||
|
||||
assert Pleroma.User.get_by_ap_id(account["url"]).accepts_direct_messages_from ==
|
||||
:people_i_follow
|
||||
end
|
||||
|
||||
test "changing to an unsupported value", %{conn: conn} do
|
||||
conn
|
||||
|> patch("/api/v1/accounts/update_credentials", %{
|
||||
accepts_direct_messages_from: "unsupported"
|
||||
})
|
||||
|> json_response(400)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue