aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/pleroma/ecto_enums.ex3
-rw-r--r--lib/pleroma/emoji-test.txt156
-rw-r--r--lib/pleroma/formatter.ex42
-rw-r--r--lib/pleroma/telemetry/logger.ex70
-rw-r--r--lib/pleroma/user.ex107
-rw-r--r--lib/pleroma/user/query.ex18
-rw-r--r--lib/pleroma/user_relationship.ex9
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex21
-rw-r--r--lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex2
-rw-r--r--lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex132
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex8
-rw-r--r--lib/pleroma/web/api_spec/operations/account_operation.ex63
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex49
-rw-r--r--lib/pleroma/web/api_spec/schemas/account.ex5
-rw-r--r--lib/pleroma/web/common_api.ex3
-rw-r--r--lib/pleroma/web/common_api/activity_draft.ex7
-rw-r--r--lib/pleroma/web/common_api/utils.ex15
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex43
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex25
-rw-r--r--lib/pleroma/web/mastodon_api/views/instance_view.ex8
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/account_controller.ex53
-rw-r--r--lib/pleroma/web/router.ex5
-rw-r--r--lib/pleroma/web/templates/o_auth/mfa/totp.html.eex2
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/register.html.eex8
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/show.html.eex2
-rw-r--r--lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex4
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api.ex1
27 files changed, 709 insertions, 152 deletions
diff --git a/lib/pleroma/ecto_enums.ex b/lib/pleroma/ecto_enums.ex
index 0e3e1e5de..48c609d45 100644
--- a/lib/pleroma/ecto_enums.ex
+++ b/lib/pleroma/ecto_enums.ex
@@ -10,7 +10,8 @@ defenum(Pleroma.UserRelationship.Type,
reblog_mute: 3,
notification_mute: 4,
inverse_subscription: 5,
- suggestion_dismiss: 6
+ suggestion_dismiss: 6,
+ endorsement: 7
)
defenum(Pleroma.FollowingRelationship.State,
diff --git a/lib/pleroma/emoji-test.txt b/lib/pleroma/emoji-test.txt
index d3c6d12bd..dd5493366 100644
--- a/lib/pleroma/emoji-test.txt
+++ b/lib/pleroma/emoji-test.txt
@@ -1,11 +1,11 @@
# emoji-test.txt
-# Date: 2020-09-12, 22:19:50 GMT
-# ยฉ 2020 Unicodeยฎ, Inc.
+# Date: 2021-08-26, 17:22:23 GMT
+# ยฉ 2021 Unicodeยฎ, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
# Emoji Keyboard/Display Test Data for UTS #51
-# Version: 13.1
+# Version: 14.0
#
# For documentation and usage, see http://www.unicode.org/reports/tr51
#
@@ -43,6 +43,7 @@
1F602 ; fully-qualified # ๐Ÿ˜‚ E0.6 face with tears of joy
1F642 ; fully-qualified # ๐Ÿ™‚ E1.0 slightly smiling face
1F643 ; fully-qualified # ๐Ÿ™ƒ E1.0 upside-down face
+1FAE0 ; fully-qualified # ๐Ÿซ  E14.0 melting face
1F609 ; fully-qualified # ๐Ÿ˜‰ E0.6 winking face
1F60A ; fully-qualified # ๐Ÿ˜Š E0.6 smiling face with smiling eyes
1F607 ; fully-qualified # ๐Ÿ˜‡ E1.0 smiling face with halo
@@ -68,10 +69,13 @@
1F911 ; fully-qualified # ๐Ÿค‘ E1.0 money-mouth face
# subgroup: face-hand
-1F917 ; fully-qualified # ๐Ÿค— E1.0 hugging face
+1F917 ; fully-qualified # ๐Ÿค— E1.0 smiling face with open hands
1F92D ; fully-qualified # ๐Ÿคญ E5.0 face with hand over mouth
+1FAE2 ; fully-qualified # ๐Ÿซข E14.0 face with open eyes and hand over mouth
+1FAE3 ; fully-qualified # ๐Ÿซฃ E14.0 face with peeking eye
1F92B ; fully-qualified # ๐Ÿคซ E5.0 shushing face
1F914 ; fully-qualified # ๐Ÿค” E1.0 thinking face
+1FAE1 ; fully-qualified # ๐Ÿซก E14.0 saluting face
# subgroup: face-neutral-skeptical
1F910 ; fully-qualified # ๐Ÿค E1.0 zipper-mouth face
@@ -79,6 +83,7 @@
1F610 ; fully-qualified # ๐Ÿ˜ E0.7 neutral face
1F611 ; fully-qualified # ๐Ÿ˜‘ E1.0 expressionless face
1F636 ; fully-qualified # ๐Ÿ˜ถ E1.0 face without mouth
+1FAE5 ; fully-qualified # ๐Ÿซฅ E14.0 dotted line face
1F636 200D 1F32B FE0F ; fully-qualified # ๐Ÿ˜ถโ€๐ŸŒซ๏ธ E13.1 face in clouds
1F636 200D 1F32B ; minimally-qualified # ๐Ÿ˜ถโ€๐ŸŒซ E13.1 face in clouds
1F60F ; fully-qualified # ๐Ÿ˜ E0.6 smirking face
@@ -105,7 +110,7 @@
1F975 ; fully-qualified # ๐Ÿฅต E11.0 hot face
1F976 ; fully-qualified # ๐Ÿฅถ E11.0 cold face
1F974 ; fully-qualified # ๐Ÿฅด E11.0 woozy face
-1F635 ; fully-qualified # ๐Ÿ˜ต E0.6 knocked-out face
+1F635 ; fully-qualified # ๐Ÿ˜ต E0.6 face with crossed-out eyes
1F635 200D 1F4AB ; fully-qualified # ๐Ÿ˜ตโ€๐Ÿ’ซ E13.1 face with spiral eyes
1F92F ; fully-qualified # ๐Ÿคฏ E5.0 exploding head
@@ -121,6 +126,7 @@
# subgroup: face-concerned
1F615 ; fully-qualified # ๐Ÿ˜• E1.0 confused face
+1FAE4 ; fully-qualified # ๐Ÿซค E14.0 face with diagonal mouth
1F61F ; fully-qualified # ๐Ÿ˜Ÿ E1.0 worried face
1F641 ; fully-qualified # ๐Ÿ™ E1.0 slightly frowning face
2639 FE0F ; fully-qualified # โ˜น๏ธ E0.7 frowning face
@@ -130,6 +136,7 @@
1F632 ; fully-qualified # ๐Ÿ˜ฒ E0.6 astonished face
1F633 ; fully-qualified # ๐Ÿ˜ณ E0.6 flushed face
1F97A ; fully-qualified # ๐Ÿฅบ E11.0 pleading face
+1F979 ; fully-qualified # ๐Ÿฅน E14.0 face holding back tears
1F626 ; fully-qualified # ๐Ÿ˜ฆ E1.0 frowning face with open mouth
1F627 ; fully-qualified # ๐Ÿ˜ง E1.0 anguished face
1F628 ; fully-qualified # ๐Ÿ˜จ E0.6 fearful face
@@ -232,8 +239,8 @@
1F4AD ; fully-qualified # ๐Ÿ’ญ E1.0 thought balloon
1F4A4 ; fully-qualified # ๐Ÿ’ค E0.6 zzz
-# Smileys & Emotion subtotal: 170
-# Smileys & Emotion subtotal: 170 w/o modifiers
+# Smileys & Emotion subtotal: 177
+# Smileys & Emotion subtotal: 177 w/o modifiers
# group: People & Body
@@ -269,6 +276,30 @@
1F596 1F3FD ; fully-qualified # ๐Ÿ––๐Ÿฝ E1.0 vulcan salute: medium skin tone
1F596 1F3FE ; fully-qualified # ๐Ÿ––๐Ÿพ E1.0 vulcan salute: medium-dark skin tone
1F596 1F3FF ; fully-qualified # ๐Ÿ––๐Ÿฟ E1.0 vulcan salute: dark skin tone
+1FAF1 ; fully-qualified # ๐Ÿซฑ E14.0 rightwards hand
+1FAF1 1F3FB ; fully-qualified # ๐Ÿซฑ๐Ÿป E14.0 rightwards hand: light skin tone
+1FAF1 1F3FC ; fully-qualified # ๐Ÿซฑ๐Ÿผ E14.0 rightwards hand: medium-light skin tone
+1FAF1 1F3FD ; fully-qualified # ๐Ÿซฑ๐Ÿฝ E14.0 rightwards hand: medium skin tone
+1FAF1 1F3FE ; fully-qualified # ๐Ÿซฑ๐Ÿพ E14.0 rightwards hand: medium-dark skin tone
+1FAF1 1F3FF ; fully-qualified # ๐Ÿซฑ๐Ÿฟ E14.0 rightwards hand: dark skin tone
+1FAF2 ; fully-qualified # ๐Ÿซฒ E14.0 leftwards hand
+1FAF2 1F3FB ; fully-qualified # ๐Ÿซฒ๐Ÿป E14.0 leftwards hand: light skin tone
+1FAF2 1F3FC ; fully-qualified # ๐Ÿซฒ๐Ÿผ E14.0 leftwards hand: medium-light skin tone
+1FAF2 1F3FD ; fully-qualified # ๐Ÿซฒ๐Ÿฝ E14.0 leftwards hand: medium skin tone
+1FAF2 1F3FE ; fully-qualified # ๐Ÿซฒ๐Ÿพ E14.0 leftwards hand: medium-dark skin tone
+1FAF2 1F3FF ; fully-qualified # ๐Ÿซฒ๐Ÿฟ E14.0 leftwards hand: dark skin tone
+1FAF3 ; fully-qualified # ๐Ÿซณ E14.0 palm down hand
+1FAF3 1F3FB ; fully-qualified # ๐Ÿซณ๐Ÿป E14.0 palm down hand: light skin tone
+1FAF3 1F3FC ; fully-qualified # ๐Ÿซณ๐Ÿผ E14.0 palm down hand: medium-light skin tone
+1FAF3 1F3FD ; fully-qualified # ๐Ÿซณ๐Ÿฝ E14.0 palm down hand: medium skin tone
+1FAF3 1F3FE ; fully-qualified # ๐Ÿซณ๐Ÿพ E14.0 palm down hand: medium-dark skin tone
+1FAF3 1F3FF ; fully-qualified # ๐Ÿซณ๐Ÿฟ E14.0 palm down hand: dark skin tone
+1FAF4 ; fully-qualified # ๐Ÿซด E14.0 palm up hand
+1FAF4 1F3FB ; fully-qualified # ๐Ÿซด๐Ÿป E14.0 palm up hand: light skin tone
+1FAF4 1F3FC ; fully-qualified # ๐Ÿซด๐Ÿผ E14.0 palm up hand: medium-light skin tone
+1FAF4 1F3FD ; fully-qualified # ๐Ÿซด๐Ÿฝ E14.0 palm up hand: medium skin tone
+1FAF4 1F3FE ; fully-qualified # ๐Ÿซด๐Ÿพ E14.0 palm up hand: medium-dark skin tone
+1FAF4 1F3FF ; fully-qualified # ๐Ÿซด๐Ÿฟ E14.0 palm up hand: dark skin tone
# subgroup: hand-fingers-partial
1F44C ; fully-qualified # ๐Ÿ‘Œ E0.6 OK hand
@@ -302,6 +333,12 @@
1F91E 1F3FD ; fully-qualified # ๐Ÿคž๐Ÿฝ E3.0 crossed fingers: medium skin tone
1F91E 1F3FE ; fully-qualified # ๐Ÿคž๐Ÿพ E3.0 crossed fingers: medium-dark skin tone
1F91E 1F3FF ; fully-qualified # ๐Ÿคž๐Ÿฟ E3.0 crossed fingers: dark skin tone
+1FAF0 ; fully-qualified # ๐Ÿซฐ E14.0 hand with index finger and thumb crossed
+1FAF0 1F3FB ; fully-qualified # ๐Ÿซฐ๐Ÿป E14.0 hand with index finger and thumb crossed: light skin tone
+1FAF0 1F3FC ; fully-qualified # ๐Ÿซฐ๐Ÿผ E14.0 hand with index finger and thumb crossed: medium-light skin tone
+1FAF0 1F3FD ; fully-qualified # ๐Ÿซฐ๐Ÿฝ E14.0 hand with index finger and thumb crossed: medium skin tone
+1FAF0 1F3FE ; fully-qualified # ๐Ÿซฐ๐Ÿพ E14.0 hand with index finger and thumb crossed: medium-dark skin tone
+1FAF0 1F3FF ; fully-qualified # ๐Ÿซฐ๐Ÿฟ E14.0 hand with index finger and thumb crossed: dark skin tone
1F91F ; fully-qualified # ๐ŸคŸ E5.0 love-you gesture
1F91F 1F3FB ; fully-qualified # ๐ŸคŸ๐Ÿป E5.0 love-you gesture: light skin tone
1F91F 1F3FC ; fully-qualified # ๐ŸคŸ๐Ÿผ E5.0 love-you gesture: medium-light skin tone
@@ -359,6 +396,12 @@
261D 1F3FD ; fully-qualified # โ˜๐Ÿฝ E1.0 index pointing up: medium skin tone
261D 1F3FE ; fully-qualified # โ˜๐Ÿพ E1.0 index pointing up: medium-dark skin tone
261D 1F3FF ; fully-qualified # โ˜๐Ÿฟ E1.0 index pointing up: dark skin tone
+1FAF5 ; fully-qualified # ๐Ÿซต E14.0 index pointing at the viewer
+1FAF5 1F3FB ; fully-qualified # ๐Ÿซต๐Ÿป E14.0 index pointing at the viewer: light skin tone
+1FAF5 1F3FC ; fully-qualified # ๐Ÿซต๐Ÿผ E14.0 index pointing at the viewer: medium-light skin tone
+1FAF5 1F3FD ; fully-qualified # ๐Ÿซต๐Ÿฝ E14.0 index pointing at the viewer: medium skin tone
+1FAF5 1F3FE ; fully-qualified # ๐Ÿซต๐Ÿพ E14.0 index pointing at the viewer: medium-dark skin tone
+1FAF5 1F3FF ; fully-qualified # ๐Ÿซต๐Ÿฟ E14.0 index pointing at the viewer: dark skin tone
# subgroup: hand-fingers-closed
1F44D ; fully-qualified # ๐Ÿ‘ E0.6 thumbs up
@@ -411,6 +454,12 @@
1F64C 1F3FD ; fully-qualified # ๐Ÿ™Œ๐Ÿฝ E1.0 raising hands: medium skin tone
1F64C 1F3FE ; fully-qualified # ๐Ÿ™Œ๐Ÿพ E1.0 raising hands: medium-dark skin tone
1F64C 1F3FF ; fully-qualified # ๐Ÿ™Œ๐Ÿฟ E1.0 raising hands: dark skin tone
+1FAF6 ; fully-qualified # ๐Ÿซถ E14.0 heart hands
+1FAF6 1F3FB ; fully-qualified # ๐Ÿซถ๐Ÿป E14.0 heart hands: light skin tone
+1FAF6 1F3FC ; fully-qualified # ๐Ÿซถ๐Ÿผ E14.0 heart hands: medium-light skin tone
+1FAF6 1F3FD ; fully-qualified # ๐Ÿซถ๐Ÿฝ E14.0 heart hands: medium skin tone
+1FAF6 1F3FE ; fully-qualified # ๐Ÿซถ๐Ÿพ E14.0 heart hands: medium-dark skin tone
+1FAF6 1F3FF ; fully-qualified # ๐Ÿซถ๐Ÿฟ E14.0 heart hands: dark skin tone
1F450 ; fully-qualified # ๐Ÿ‘ E0.6 open hands
1F450 1F3FB ; fully-qualified # ๐Ÿ‘๐Ÿป E1.0 open hands: light skin tone
1F450 1F3FC ; fully-qualified # ๐Ÿ‘๐Ÿผ E1.0 open hands: medium-light skin tone
@@ -424,6 +473,31 @@
1F932 1F3FE ; fully-qualified # ๐Ÿคฒ๐Ÿพ E5.0 palms up together: medium-dark skin tone
1F932 1F3FF ; fully-qualified # ๐Ÿคฒ๐Ÿฟ E5.0 palms up together: dark skin tone
1F91D ; fully-qualified # ๐Ÿค E3.0 handshake
+1F91D 1F3FB ; fully-qualified # ๐Ÿค๐Ÿป E3.0 handshake: light skin tone
+1F91D 1F3FC ; fully-qualified # ๐Ÿค๐Ÿผ E3.0 handshake: medium-light skin tone
+1F91D 1F3FD ; fully-qualified # ๐Ÿค๐Ÿฝ E3.0 handshake: medium skin tone
+1F91D 1F3FE ; fully-qualified # ๐Ÿค๐Ÿพ E3.0 handshake: medium-dark skin tone
+1F91D 1F3FF ; fully-qualified # ๐Ÿค๐Ÿฟ E3.0 handshake: dark skin tone
+1FAF1 1F3FB 200D 1FAF2 1F3FC ; fully-qualified # ๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿผ E14.0 handshake: light skin tone, medium-light skin tone
+1FAF1 1F3FB 200D 1FAF2 1F3FD ; fully-qualified # ๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿฝ E14.0 handshake: light skin tone, medium skin tone
+1FAF1 1F3FB 200D 1FAF2 1F3FE ; fully-qualified # ๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿพ E14.0 handshake: light skin tone, medium-dark skin tone
+1FAF1 1F3FB 200D 1FAF2 1F3FF ; fully-qualified # ๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿฟ E14.0 handshake: light skin tone, dark skin tone
+1FAF1 1F3FC 200D 1FAF2 1F3FB ; fully-qualified # ๐Ÿซฑ๐Ÿผโ€๐Ÿซฒ๐Ÿป E14.0 handshake: medium-light skin tone, light skin tone
+1FAF1 1F3FC 200D 1FAF2 1F3FD ; fully-qualified # ๐Ÿซฑ๐Ÿผโ€๐Ÿซฒ๐Ÿฝ E14.0 handshake: medium-light skin tone, medium skin tone
+1FAF1 1F3FC 200D 1FAF2 1F3FE ; fully-qualified # ๐Ÿซฑ๐Ÿผโ€๐Ÿซฒ๐Ÿพ E14.0 handshake: medium-light skin tone, medium-dark skin tone
+1FAF1 1F3FC 200D 1FAF2 1F3FF ; fully-qualified # ๐Ÿซฑ๐Ÿผโ€๐Ÿซฒ๐Ÿฟ E14.0 handshake: medium-light skin tone, dark skin tone
+1FAF1 1F3FD 200D 1FAF2 1F3FB ; fully-qualified # ๐Ÿซฑ๐Ÿฝโ€๐Ÿซฒ๐Ÿป E14.0 handshake: medium skin tone, light skin tone
+1FAF1 1F3FD 200D 1FAF2 1F3FC ; fully-qualified # ๐Ÿซฑ๐Ÿฝโ€๐Ÿซฒ๐Ÿผ E14.0 handshake: medium skin tone, medium-light skin tone
+1FAF1 1F3FD 200D 1FAF2 1F3FE ; fully-qualified # ๐Ÿซฑ๐Ÿฝโ€๐Ÿซฒ๐Ÿพ E14.0 handshake: medium skin tone, medium-dark skin tone
+1FAF1 1F3FD 200D 1FAF2 1F3FF ; fully-qualified # ๐Ÿซฑ๐Ÿฝโ€๐Ÿซฒ๐Ÿฟ E14.0 handshake: medium skin tone, dark skin tone
+1FAF1 1F3FE 200D 1FAF2 1F3FB ; fully-qualified # ๐Ÿซฑ๐Ÿพโ€๐Ÿซฒ๐Ÿป E14.0 handshake: medium-dark skin tone, light skin tone
+1FAF1 1F3FE 200D 1FAF2 1F3FC ; fully-qualified # ๐Ÿซฑ๐Ÿพโ€๐Ÿซฒ๐Ÿผ E14.0 handshake: medium-dark skin tone, medium-light skin tone
+1FAF1 1F3FE 200D 1FAF2 1F3FD ; fully-qualified # ๐Ÿซฑ๐Ÿพโ€๐Ÿซฒ๐Ÿฝ E14.0 handshake: medium-dark skin tone, medium skin tone
+1FAF1 1F3FE 200D 1FAF2 1F3FF ; fully-qualified # ๐Ÿซฑ๐Ÿพโ€๐Ÿซฒ๐Ÿฟ E14.0 handshake: medium-dark skin tone, dark skin tone
+1FAF1 1F3FF 200D 1FAF2 1F3FB ; fully-qualified # ๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿป E14.0 handshake: dark skin tone, light skin tone
+1FAF1 1F3FF 200D 1FAF2 1F3FC ; fully-qualified # ๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿผ E14.0 handshake: dark skin tone, medium-light skin tone
+1FAF1 1F3FF 200D 1FAF2 1F3FD ; fully-qualified # ๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿฝ E14.0 handshake: dark skin tone, medium skin tone
+1FAF1 1F3FF 200D 1FAF2 1F3FE ; fully-qualified # ๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿพ E14.0 handshake: dark skin tone, medium-dark skin tone
1F64F ; fully-qualified # ๐Ÿ™ E0.6 folded hands
1F64F 1F3FB ; fully-qualified # ๐Ÿ™๐Ÿป E1.0 folded hands: light skin tone
1F64F 1F3FC ; fully-qualified # ๐Ÿ™๐Ÿผ E1.0 folded hands: medium-light skin tone
@@ -501,6 +575,7 @@
1F441 ; unqualified # ๐Ÿ‘ E0.7 eye
1F445 ; fully-qualified # ๐Ÿ‘… E0.6 tongue
1F444 ; fully-qualified # ๐Ÿ‘„ E0.6 mouth
+1FAE6 ; fully-qualified # ๐Ÿซฆ E14.0 biting lip
# subgroup: person
1F476 ; fully-qualified # ๐Ÿ‘ถ E0.6 baby
@@ -1472,6 +1547,12 @@
1F477 1F3FE 200D 2640 ; minimally-qualified # ๐Ÿ‘ท๐Ÿพโ€โ™€ E4.0 woman construction worker: medium-dark skin tone
1F477 1F3FF 200D 2640 FE0F ; fully-qualified # ๐Ÿ‘ท๐Ÿฟโ€โ™€๏ธ E4.0 woman construction worker: dark skin tone
1F477 1F3FF 200D 2640 ; minimally-qualified # ๐Ÿ‘ท๐Ÿฟโ€โ™€ E4.0 woman construction worker: dark skin tone
+1FAC5 ; fully-qualified # ๐Ÿซ… E14.0 person with crown
+1FAC5 1F3FB ; fully-qualified # ๐Ÿซ…๐Ÿป E14.0 person with crown: light skin tone
+1FAC5 1F3FC ; fully-qualified # ๐Ÿซ…๐Ÿผ E14.0 person with crown: medium-light skin tone
+1FAC5 1F3FD ; fully-qualified # ๐Ÿซ…๐Ÿฝ E14.0 person with crown: medium skin tone
+1FAC5 1F3FE ; fully-qualified # ๐Ÿซ…๐Ÿพ E14.0 person with crown: medium-dark skin tone
+1FAC5 1F3FF ; fully-qualified # ๐Ÿซ…๐Ÿฟ E14.0 person with crown: dark skin tone
1F934 ; fully-qualified # ๐Ÿคด E3.0 prince
1F934 1F3FB ; fully-qualified # ๐Ÿคด๐Ÿป E3.0 prince: light skin tone
1F934 1F3FC ; fully-qualified # ๐Ÿคด๐Ÿผ E3.0 prince: medium-light skin tone
@@ -1592,6 +1673,18 @@
1F930 1F3FD ; fully-qualified # ๐Ÿคฐ๐Ÿฝ E3.0 pregnant woman: medium skin tone
1F930 1F3FE ; fully-qualified # ๐Ÿคฐ๐Ÿพ E3.0 pregnant woman: medium-dark skin tone
1F930 1F3FF ; fully-qualified # ๐Ÿคฐ๐Ÿฟ E3.0 pregnant woman: dark skin tone
+1FAC3 ; fully-qualified # ๐Ÿซƒ E14.0 pregnant man
+1FAC3 1F3FB ; fully-qualified # ๐Ÿซƒ๐Ÿป E14.0 pregnant man: light skin tone
+1FAC3 1F3FC ; fully-qualified # ๐Ÿซƒ๐Ÿผ E14.0 pregnant man: medium-light skin tone
+1FAC3 1F3FD ; fully-qualified # ๐Ÿซƒ๐Ÿฝ E14.0 pregnant man: medium skin tone
+1FAC3 1F3FE ; fully-qualified # ๐Ÿซƒ๐Ÿพ E14.0 pregnant man: medium-dark skin tone
+1FAC3 1F3FF ; fully-qualified # ๐Ÿซƒ๐Ÿฟ E14.0 pregnant man: dark skin tone
+1FAC4 ; fully-qualified # ๐Ÿซ„ E14.0 pregnant person
+1FAC4 1F3FB ; fully-qualified # ๐Ÿซ„๐Ÿป E14.0 pregnant person: light skin tone
+1FAC4 1F3FC ; fully-qualified # ๐Ÿซ„๐Ÿผ E14.0 pregnant person: medium-light skin tone
+1FAC4 1F3FD ; fully-qualified # ๐Ÿซ„๐Ÿฝ E14.0 pregnant person: medium skin tone
+1FAC4 1F3FE ; fully-qualified # ๐Ÿซ„๐Ÿพ E14.0 pregnant person: medium-dark skin tone
+1FAC4 1F3FF ; fully-qualified # ๐Ÿซ„๐Ÿฟ E14.0 pregnant person: dark skin tone
1F931 ; fully-qualified # ๐Ÿคฑ E5.0 breast-feeding
1F931 1F3FB ; fully-qualified # ๐Ÿคฑ๐Ÿป E5.0 breast-feeding: light skin tone
1F931 1F3FC ; fully-qualified # ๐Ÿคฑ๐Ÿผ E5.0 breast-feeding: medium-light skin tone
@@ -1862,6 +1955,7 @@
1F9DF 200D 2642 ; minimally-qualified # ๐ŸงŸโ€โ™‚ E5.0 man zombie
1F9DF 200D 2640 FE0F ; fully-qualified # ๐ŸงŸโ€โ™€๏ธ E5.0 woman zombie
1F9DF 200D 2640 ; minimally-qualified # ๐ŸงŸโ€โ™€ E5.0 woman zombie
+1F9CC ; fully-qualified # ๐ŸงŒ E14.0 troll
# subgroup: person-activity
1F486 ; fully-qualified # ๐Ÿ’† E0.6 person getting massage
@@ -3168,8 +3262,8 @@
1FAC2 ; fully-qualified # ๐Ÿซ‚ E13.0 people hugging
1F463 ; fully-qualified # ๐Ÿ‘ฃ E0.6 footprints
-# People & Body subtotal: 2899
-# People & Body subtotal: 494 w/o modifiers
+# People & Body subtotal: 2986
+# People & Body subtotal: 506 w/o modifiers
# group: Component
@@ -3304,6 +3398,7 @@
1F988 ; fully-qualified # ๐Ÿฆˆ E3.0 shark
1F419 ; fully-qualified # ๐Ÿ™ E0.6 octopus
1F41A ; fully-qualified # ๐Ÿš E0.6 spiral shell
+1FAB8 ; fully-qualified # ๐Ÿชธ E14.0 coral
# subgroup: animal-bug
1F40C ; fully-qualified # ๐ŸŒ E0.6 snail
@@ -3329,6 +3424,7 @@
1F490 ; fully-qualified # ๐Ÿ’ E0.6 bouquet
1F338 ; fully-qualified # ๐ŸŒธ E0.6 cherry blossom
1F4AE ; fully-qualified # ๐Ÿ’ฎ E0.6 white flower
+1FAB7 ; fully-qualified # ๐Ÿชท E14.0 lotus
1F3F5 FE0F ; fully-qualified # ๐Ÿต๏ธ E0.7 rosette
1F3F5 ; unqualified # ๐Ÿต E0.7 rosette
1F339 ; fully-qualified # ๐ŸŒน E0.6 rose
@@ -3353,9 +3449,11 @@
1F341 ; fully-qualified # ๐Ÿ E0.6 maple leaf
1F342 ; fully-qualified # ๐Ÿ‚ E0.6 fallen leaf
1F343 ; fully-qualified # ๐Ÿƒ E0.6 leaf fluttering in wind
+1FAB9 ; fully-qualified # ๐Ÿชน E14.0 empty nest
+1FABA ; fully-qualified # ๐Ÿชบ E14.0 nest with eggs
-# Animals & Nature subtotal: 147
-# Animals & Nature subtotal: 147 w/o modifiers
+# Animals & Nature subtotal: 151
+# Animals & Nature subtotal: 151 w/o modifiers
# group: Food & Drink
@@ -3396,6 +3494,7 @@
1F9C5 ; fully-qualified # ๐Ÿง… E12.0 onion
1F344 ; fully-qualified # ๐Ÿ„ E0.6 mushroom
1F95C ; fully-qualified # ๐Ÿฅœ E3.0 peanuts
+1FAD8 ; fully-qualified # ๐Ÿซ˜ E14.0 beans
1F330 ; fully-qualified # ๐ŸŒฐ E0.6 chestnut
# subgroup: food-prepared
@@ -3491,6 +3590,7 @@
1F37B ; fully-qualified # ๐Ÿป E0.6 clinking beer mugs
1F942 ; fully-qualified # ๐Ÿฅ‚ E3.0 clinking glasses
1F943 ; fully-qualified # ๐Ÿฅƒ E3.0 tumbler glass
+1FAD7 ; fully-qualified # ๐Ÿซ— E14.0 pouring liquid
1F964 ; fully-qualified # ๐Ÿฅค E5.0 cup with straw
1F9CB ; fully-qualified # ๐Ÿง‹ E13.0 bubble tea
1F9C3 ; fully-qualified # ๐Ÿงƒ E12.0 beverage box
@@ -3504,10 +3604,11 @@
1F374 ; fully-qualified # ๐Ÿด E0.6 fork and knife
1F944 ; fully-qualified # ๐Ÿฅ„ E3.0 spoon
1F52A ; fully-qualified # ๐Ÿ”ช E0.6 kitchen knife
+1FAD9 ; fully-qualified # ๐Ÿซ™ E14.0 jar
1F3FA ; fully-qualified # ๐Ÿบ E1.0 amphora
-# Food & Drink subtotal: 131
-# Food & Drink subtotal: 131 w/o modifiers
+# Food & Drink subtotal: 134
+# Food & Drink subtotal: 134 w/o modifiers
# group: Travel & Places
@@ -3597,6 +3698,7 @@
2668 FE0F ; fully-qualified # โ™จ๏ธ E0.6 hot springs
2668 ; unqualified # โ™จ E0.6 hot springs
1F3A0 ; fully-qualified # ๐ŸŽ  E0.6 carousel horse
+1F6DD ; fully-qualified # ๐Ÿ› E14.0 playground slide
1F3A1 ; fully-qualified # ๐ŸŽก E0.6 ferris wheel
1F3A2 ; fully-qualified # ๐ŸŽข E0.6 roller coaster
1F488 ; fully-qualified # ๐Ÿ’ˆ E0.6 barber pole
@@ -3652,6 +3754,7 @@
1F6E2 FE0F ; fully-qualified # ๐Ÿ›ข๏ธ E0.7 oil drum
1F6E2 ; unqualified # ๐Ÿ›ข E0.7 oil drum
26FD ; fully-qualified # โ›ฝ E0.6 fuel pump
+1F6DE ; fully-qualified # ๐Ÿ›ž E14.0 wheel
1F6A8 ; fully-qualified # ๐Ÿšจ E0.6 police car light
1F6A5 ; fully-qualified # ๐Ÿšฅ E0.6 horizontal traffic light
1F6A6 ; fully-qualified # ๐Ÿšฆ E1.0 vertical traffic light
@@ -3660,6 +3763,7 @@
# subgroup: transport-water
2693 ; fully-qualified # โš“ E0.6 anchor
+1F6DF ; fully-qualified # ๐Ÿ›Ÿ E14.0 ring buoy
26F5 ; fully-qualified # โ›ต E0.6 sailboat
1F6F6 ; fully-qualified # ๐Ÿ›ถ E3.0 canoe
1F6A4 ; fully-qualified # ๐Ÿšค E0.6 speedboat
@@ -3797,8 +3901,8 @@
1F4A7 ; fully-qualified # ๐Ÿ’ง E0.6 droplet
1F30A ; fully-qualified # ๐ŸŒŠ E0.6 water wave
-# Travel & Places subtotal: 264
-# Travel & Places subtotal: 264 w/o modifiers
+# Travel & Places subtotal: 267
+# Travel & Places subtotal: 267 w/o modifiers
# group: Activities
@@ -3874,6 +3978,7 @@
1F52E ; fully-qualified # ๐Ÿ”ฎ E0.6 crystal ball
1FA84 ; fully-qualified # ๐Ÿช„ E13.0 magic wand
1F9FF ; fully-qualified # ๐Ÿงฟ E11.0 nazar amulet
+1FAAC ; fully-qualified # ๐Ÿชฌ E14.0 hamsa
1F3AE ; fully-qualified # ๐ŸŽฎ E0.6 video game
1F579 FE0F ; fully-qualified # ๐Ÿ•น๏ธ E0.7 joystick
1F579 ; unqualified # ๐Ÿ•น E0.7 joystick
@@ -3882,6 +3987,7 @@
1F9E9 ; fully-qualified # ๐Ÿงฉ E11.0 puzzle piece
1F9F8 ; fully-qualified # ๐Ÿงธ E11.0 teddy bear
1FA85 ; fully-qualified # ๐Ÿช… E13.0 piรฑata
+1FAA9 ; fully-qualified # ๐Ÿชฉ E14.0 mirror ball
1FA86 ; fully-qualified # ๐Ÿช† E13.0 nesting dolls
2660 FE0F ; fully-qualified # โ™ ๏ธ E0.6 spade suit
2660 ; unqualified # โ™  E0.6 spade suit
@@ -3907,8 +4013,8 @@
1F9F6 ; fully-qualified # ๐Ÿงถ E11.0 yarn
1FAA2 ; fully-qualified # ๐Ÿชข E13.0 knot
-# Activities subtotal: 95
-# Activities subtotal: 95 w/o modifiers
+# Activities subtotal: 97
+# Activities subtotal: 97 w/o modifiers
# group: Objects
@@ -4009,6 +4115,7 @@
# subgroup: computer
1F50B ; fully-qualified # ๐Ÿ”‹ E0.6 battery
+1FAAB ; fully-qualified # ๐Ÿชซ E14.0 low battery
1F50C ; fully-qualified # ๐Ÿ”Œ E0.6 electric plug
1F4BB ; fully-qualified # ๐Ÿ’ป E0.6 laptop
1F5A5 FE0F ; fully-qualified # ๐Ÿ–ฅ๏ธ E0.7 desktop computer
@@ -4207,7 +4314,9 @@
1FA78 ; fully-qualified # ๐Ÿฉธ E12.0 drop of blood
1F48A ; fully-qualified # ๐Ÿ’Š E0.6 pill
1FA79 ; fully-qualified # ๐Ÿฉน E12.0 adhesive bandage
+1FA7C ; fully-qualified # ๐Ÿฉผ E14.0 crutch
1FA7A ; fully-qualified # ๐Ÿฉบ E12.0 stethoscope
+1FA7B ; fully-qualified # ๐Ÿฉป E14.0 x-ray
# subgroup: household
1F6AA ; fully-qualified # ๐Ÿšช E0.6 door
@@ -4232,6 +4341,7 @@
1F9FB ; fully-qualified # ๐Ÿงป E11.0 roll of paper
1FAA3 ; fully-qualified # ๐Ÿชฃ E13.0 bucket
1F9FC ; fully-qualified # ๐Ÿงผ E11.0 soap
+1FAE7 ; fully-qualified # ๐Ÿซง E14.0 bubbles
1FAA5 ; fully-qualified # ๐Ÿชฅ E13.0 toothbrush
1F9FD ; fully-qualified # ๐Ÿงฝ E11.0 sponge
1F9EF ; fully-qualified # ๐Ÿงฏ E11.0 fire extinguisher
@@ -4246,9 +4356,10 @@
26B1 ; unqualified # โšฑ E1.0 funeral urn
1F5FF ; fully-qualified # ๐Ÿ—ฟ E0.6 moai
1FAA7 ; fully-qualified # ๐Ÿชง E13.0 placard
+1FAAA ; fully-qualified # ๐Ÿชช E14.0 identification card
-# Objects subtotal: 299
-# Objects subtotal: 299 w/o modifiers
+# Objects subtotal: 304
+# Objects subtotal: 304 w/o modifiers
# group: Symbols
@@ -4409,6 +4520,7 @@
2795 ; fully-qualified # โž• E0.6 plus
2796 ; fully-qualified # โž– E0.6 minus
2797 ; fully-qualified # โž— E0.6 divide
+1F7F0 ; fully-qualified # ๐ŸŸฐ E14.0 heavy equals sign
267E FE0F ; fully-qualified # โ™พ๏ธ E11.0 infinity
267E ; unqualified # โ™พ E11.0 infinity
@@ -4581,8 +4693,8 @@
1F533 ; fully-qualified # ๐Ÿ”ณ E0.6 white square button
1F532 ; fully-qualified # ๐Ÿ”ฒ E0.6 black square button
-# Symbols subtotal: 301
-# Symbols subtotal: 301 w/o modifiers
+# Symbols subtotal: 302
+# Symbols subtotal: 302 w/o modifiers
# group: Flags
@@ -4871,7 +4983,7 @@
# Flags subtotal: 275 w/o modifiers
# Status Counts
-# fully-qualified : 3512
+# fully-qualified : 3624
# minimally-qualified : 817
# unqualified : 252
# component : 9
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index ae37946ab..115835378 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -34,32 +34,34 @@ defmodule Pleroma.Formatter do
def mention_handler("@" <> nickname, buffer, opts, acc) do
case User.get_cached_by_nickname(nickname) do
- %User{id: id} = user ->
- user_url = user.uri || user.ap_id
- nickname_text = get_nickname_text(nickname, opts)
-
- link =
- Phoenix.HTML.Tag.content_tag(
- :span,
- Phoenix.HTML.Tag.content_tag(
- :a,
- ["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)],
- "data-user": id,
- class: "u-url mention",
- href: user_url,
- rel: "ugc"
- ),
- class: "h-card"
- )
- |> Phoenix.HTML.safe_to_string()
-
- {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}
+ %User{} = user ->
+ {mention_from_user(user, opts),
+ %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}
_ ->
{buffer, acc}
end
end
+ def mention_from_user(%User{id: id} = user, opts \\ %{mentions_format: :full}) do
+ user_url = user.uri || user.ap_id
+ nickname_text = get_nickname_text(user.nickname, opts)
+
+ Phoenix.HTML.Tag.content_tag(
+ :span,
+ Phoenix.HTML.Tag.content_tag(
+ :a,
+ ["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)],
+ "data-user": id,
+ class: "u-url mention",
+ href: user_url,
+ rel: "ugc"
+ ),
+ class: "h-card"
+ )
+ |> Phoenix.HTML.safe_to_string()
+ end
+
def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
tag = String.downcase(tag)
url = "#{Pleroma.Web.Endpoint.url()}/tag/#{tag}"
diff --git a/lib/pleroma/telemetry/logger.ex b/lib/pleroma/telemetry/logger.ex
index 35e245237..10165c1b2 100644
--- a/lib/pleroma/telemetry/logger.ex
+++ b/lib/pleroma/telemetry/logger.ex
@@ -12,16 +12,10 @@ defmodule Pleroma.Telemetry.Logger do
[:pleroma, :connection_pool, :reclaim, :stop],
[:pleroma, :connection_pool, :provision_failure],
[:pleroma, :connection_pool, :client, :dead],
- [:pleroma, :connection_pool, :client, :add],
- [:pleroma, :repo, :query]
+ [:pleroma, :connection_pool, :client, :add]
]
def attach do
- :telemetry.attach_many(
- "pleroma-logger",
- @events,
- &Pleroma.Telemetry.Logger.handle_event/4,
- []
- )
+ :telemetry.attach_many("pleroma-logger", @events, &handle_event/4, [])
end
# Passing anonymous functions instead of strings to logger is intentional,
@@ -93,64 +87,4 @@ defmodule Pleroma.Telemetry.Logger do
end
def handle_event([:pleroma, :connection_pool, :client, :add], _, _, _), do: :ok
-
- def handle_event(
- [:pleroma, :repo, :query] = _name,
- %{query_time: query_time} = measurements,
- %{source: source} = metadata,
- config
- ) do
- logging_config = Pleroma.Config.get([:telemetry, :slow_queries_logging], [])
-
- if logging_config[:enabled] &&
- logging_config[:min_duration] &&
- query_time > logging_config[:min_duration] and
- (is_nil(logging_config[:exclude_sources]) or
- source not in logging_config[:exclude_sources]) do
- log_slow_query(measurements, metadata, config)
- else
- :ok
- end
- end
-
- defp log_slow_query(
- %{query_time: query_time} = _measurements,
- %{source: _source, query: query, params: query_params, repo: repo} = _metadata,
- _config
- ) do
- sql_explain =
- with {:ok, %{rows: explain_result_rows}} <-
- repo.query("EXPLAIN " <> query, query_params, log: false) do
- Enum.map_join(explain_result_rows, "\n", & &1)
- end
-
- {:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace)
-
- pleroma_stacktrace =
- Enum.filter(stacktrace, fn
- {__MODULE__, _, _, _} ->
- false
-
- {mod, _, _, _} ->
- mod
- |> to_string()
- |> String.starts_with?("Elixir.Pleroma.")
- end)
-
- Logger.warn(fn ->
- """
- Slow query!
-
- Total time: #{round(query_time / 1_000)} ms
-
- #{query}
-
- #{inspect(query_params, limit: :infinity)}
-
- #{sql_explain}
-
- #{Exception.format_stacktrace(pleroma_stacktrace)}
- """
- end)
- end
end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 390de1e2d..36177bda3 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -78,6 +78,10 @@ defmodule Pleroma.User do
inverse_subscription: [
subscribee_subscriptions: :subscriber_users,
subscriber_subscriptions: :subscribee_users
+ ],
+ endorsement: [
+ endorser_endorsements: :endorsed_users,
+ endorsee_endorsements: :endorser_users
]
]
@@ -150,6 +154,8 @@ defmodule Pleroma.User do
field(:pinned_objects, :map, default: %{})
field(:is_suggested, :boolean, default: false)
field(:last_status_at, :naive_datetime)
+ field(:birthday, :date)
+ field(:show_birthday, :boolean, default: false)
embeds_one(
:notification_settings,
@@ -170,25 +176,25 @@ defmodule Pleroma.User do
{incoming_relation, incoming_relation_source}
]} <- @user_relationships_config do
# Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
- # :notification_muter_mutes, :subscribee_subscriptions
+ # :notification_muter_mutes, :subscribee_subscriptions, :endorser_endorsements
has_many(outgoing_relation, UserRelationship,
foreign_key: :source_id,
where: [relationship_type: relationship_type]
)
# Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
- # :notification_mutee_mutes, :subscriber_subscriptions
+ # :notification_mutee_mutes, :subscriber_subscriptions, :endorsee_endorsements
has_many(incoming_relation, UserRelationship,
foreign_key: :target_id,
where: [relationship_type: relationship_type]
)
# Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
- # :notification_muted_users, :subscriber_users
+ # :notification_muted_users, :subscriber_users, :endorsed_users
has_many(outgoing_relation_target, through: [outgoing_relation, :target])
# Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
- # :notification_muter_users, :subscribee_users
+ # :notification_muter_users, :subscribee_users, :endorser_users
has_many(incoming_relation_source, through: [incoming_relation, :source])
end
@@ -216,7 +222,7 @@ defmodule Pleroma.User do
@user_relationships_config do
# `def blocked_users_relation/2`, `def muted_users_relation/2`,
# `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
- # `def subscriber_users/2`
+ # `def subscriber_users/2`, `def endorsed_users_relation/2`
def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
target_users_query = assoc(user, unquote(outgoing_relation_target))
@@ -229,7 +235,7 @@ defmodule Pleroma.User do
end
# `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
- # `def notification_muted_users/2`, `def subscriber_users/2`
+ # `def notification_muted_users/2`, `def subscriber_users/2`, `def endorsed_users/2`
def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
__MODULE__
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [
@@ -240,7 +246,8 @@ defmodule Pleroma.User do
end
# `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
- # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
+ # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`,
+ # `def endorsed_users_ap_ids/2`
def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
__MODULE__
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [
@@ -465,7 +472,9 @@ defmodule Pleroma.User do
:actor_type,
:also_known_as,
:accepts_chat_messages,
- :pinned_objects
+ :pinned_objects,
+ :birthday,
+ :show_birthday
]
)
|> cast(params, [:name], empty_values: [])
@@ -526,9 +535,12 @@ defmodule Pleroma.User do
:is_discoverable,
:actor_type,
:accepts_chat_messages,
- :disclose_client
+ :disclose_client,
+ :birthday,
+ :show_birthday
]
)
+ |> validate_min_age()
|> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: bio_limit)
@@ -733,7 +745,8 @@ defmodule Pleroma.User do
:password_confirmation,
:emoji,
:accepts_chat_messages,
- :registration_reason
+ :registration_reason,
+ :birthday
])
|> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password)
@@ -755,6 +768,8 @@ defmodule Pleroma.User do
|> validate_length(:name, min: 1, max: name_limit)
|> validate_length(:registration_reason, max: reason_limit)
|> maybe_validate_required_email(opts[:external])
+ |> maybe_validate_required_birthday
+ |> validate_min_age()
|> put_password_hash
|> put_ap_id()
|> unique_constraint(:ap_id)
@@ -771,6 +786,26 @@ defmodule Pleroma.User do
end
end
+ defp maybe_validate_required_birthday(changeset) do
+ if Config.get([:instance, :birthday_required]) do
+ validate_required(changeset, [:birthday])
+ else
+ changeset
+ end
+ end
+
+ defp validate_min_age(changeset) do
+ changeset
+ |> validate_change(:birthday, fn :birthday, birthday ->
+ valid? =
+ Date.utc_today()
+ |> Date.diff(birthday) >=
+ Config.get([:instance, :birthday_min_age])
+
+ if valid?, do: [], else: [birthday: "Invalid age"]
+ end)
+ end
+
defp put_ap_id(changeset) do
ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
put_change(changeset, :ap_id, ap_id)
@@ -1050,6 +1085,10 @@ defmodule Pleroma.User do
Repo.get_by(User, ap_id: ap_id)
end
+ def get_by_uri(uri) do
+ Repo.get_by(User, uri: uri)
+ end
+
def get_all_by_ap_id(ap_ids) do
from(u in __MODULE__,
where: u.ap_id in ^ap_ids
@@ -1516,6 +1555,40 @@ defmodule Pleroma.User do
unblock(blocker, get_cached_by_ap_id(ap_id))
end
+ def endorse(%User{} = endorser, %User{} = target) do
+ with max_endorsed_users <- Pleroma.Config.get([:instance, :max_endorsed_users], 0),
+ endorsed_users <-
+ User.endorsed_users_relation(endorser)
+ |> Repo.aggregate(:count, :id) do
+ cond do
+ endorsed_users >= max_endorsed_users ->
+ {:error, "You have already pinned the maximum number of users"}
+
+ not following?(endorser, target) ->
+ {:error, "Could not endorse: You are not following #{target.nickname}"}
+
+ true ->
+ UserRelationship.create_endorsement(endorser, target)
+ end
+ end
+ end
+
+ def endorse(%User{} = endorser, %{ap_id: ap_id}) do
+ with %User{} = endorsed <- get_cached_by_ap_id(ap_id) do
+ endorse(endorser, endorsed)
+ end
+ end
+
+ def unendorse(%User{} = unendorser, %User{} = target) do
+ UserRelationship.delete_endorsement(unendorser, target)
+ end
+
+ def unendorse(%User{} = unendorser, %{ap_id: ap_id}) do
+ with %User{} = user <- get_cached_by_ap_id(ap_id) do
+ unendorse(unendorser, user)
+ end
+ end
+
def mutes?(nil, _), do: false
def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
@@ -1561,6 +1634,10 @@ defmodule Pleroma.User do
end
end
+ def endorses?(%User{} = user, %User{} = target) do
+ UserRelationship.endorsement_exists?(user, target)
+ end
+
@doc """
Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
@@ -2232,6 +2309,7 @@ defmodule Pleroma.User do
def get_ap_ids_by_nicknames(nicknames) do
from(u in User,
where: u.nickname in ^nicknames,
+ order_by: fragment("array_position(?, ?)", ^nicknames, u.nickname),
select: u.ap_id
)
|> Repo.all()
@@ -2512,4 +2590,13 @@ defmodule Pleroma.User do
_ -> {:error, user}
end
end
+
+ def get_friends_birthdays_query(%User{} = user, day, month) do
+ User.Query.build(%{
+ friends: user,
+ deactivated: false,
+ birthday_day: day,
+ birthday_month: month
+ })
+ end
end
diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex
index bf78cb32d..bd11d287c 100644
--- a/lib/pleroma/user/query.ex
+++ b/lib/pleroma/user/query.ex
@@ -59,7 +59,9 @@ defmodule Pleroma.User.Query do
order_by: term(),
select: term(),
limit: pos_integer(),
- actor_types: [String.t()]
+ actor_types: [String.t()],
+ birthday_day: pos_integer(),
+ birthday_month: pos_integer()
}
| map()
@@ -230,6 +232,20 @@ defmodule Pleroma.User.Query do
|> where([u], not like(u.nickname, "internal.%"))
end
+ defp compose_query({:birthday_day, day}, query) do
+ query
+ |> where([u], u.show_birthday == true)
+ |> where([u], not is_nil(u.birthday))
+ |> where([u], fragment("date_part('day', ?)", u.birthday) == ^day)
+ end
+
+ defp compose_query({:birthday_month, month}, query) do
+ query
+ |> where([u], u.show_birthday == true)
+ |> where([u], not is_nil(u.birthday))
+ |> where([u], fragment("date_part('month', ?)", u.birthday) == ^month)
+ end
+
defp compose_query(_unsupported_param, query), do: query
defp location_query(query, local) do
diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex
index a467e9b65..8be5acc59 100644
--- a/lib/pleroma/user_relationship.ex
+++ b/lib/pleroma/user_relationship.ex
@@ -24,17 +24,20 @@ defmodule Pleroma.UserRelationship do
for relationship_type <- Keyword.keys(Pleroma.UserRelationship.Type.__enum_map__()) do
# `def create_block/2`, `def create_mute/2`, `def create_reblog_mute/2`,
- # `def create_notification_mute/2`, `def create_inverse_subscription/2`
+ # `def create_notification_mute/2`, `def create_inverse_subscription/2`,
+ # `def endorsement/2`
def unquote(:"create_#{relationship_type}")(source, target),
do: create(unquote(relationship_type), source, target)
# `def delete_block/2`, `def delete_mute/2`, `def delete_reblog_mute/2`,
- # `def delete_notification_mute/2`, `def delete_inverse_subscription/2`
+ # `def delete_notification_mute/2`, `def delete_inverse_subscription/2`,
+ # `def delete_endorsement/2`
def unquote(:"delete_#{relationship_type}")(source, target),
do: delete(unquote(relationship_type), source, target)
# `def block_exists?/2`, `def mute_exists?/2`, `def reblog_mute_exists?/2`,
- # `def notification_mute_exists?/2`, `def inverse_subscription_exists?/2`
+ # `def notification_mute_exists?/2`, `def inverse_subscription_exists?/2`,
+ # `def inverse_endorsement?/2`
def unquote(:"#{relationship_type}_exists?")(source, target),
do: exists?(unquote(relationship_type), source, target)
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 756096952..e6475a2b7 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1501,6 +1501,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
nil
end
+ birthday =
+ if is_binary(data["vcard:bday"]) do
+ case Date.from_iso8601(data["vcard:bday"]) do
+ {:ok, date} -> date
+ {:error, _} -> nil
+ end
+ else
+ nil
+ end
+
+ show_birthday = !!birthday
+
user_data = %{
ap_id: data["id"],
uri: get_actor_url(data["url"]),
@@ -1523,7 +1535,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
inbox: data["inbox"],
shared_inbox: shared_inbox,
accepts_chat_messages: accepts_chat_messages,
- pinned_objects: pinned_objects
+ pinned_objects: pinned_objects,
+ birthday: birthday,
+ show_birthday: show_birthday
}
# nickname can be nil because of virtual actors
@@ -1664,7 +1678,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
"orderedItems" => objects
})
when type in ["OrderedCollection", "Collection"] do
- Map.new(objects, fn %{"id" => object_ap_id} -> {object_ap_id, NaiveDateTime.utc_now()} end)
+ Map.new(objects, fn
+ %{"id" => object_ap_id} -> {object_ap_id, NaiveDateTime.utc_now()}
+ object_ap_id when is_binary(object_ap_id) -> {object_ap_id, NaiveDateTime.utc_now()}
+ end)
end
def fetch_and_prepare_featured_from_ap_id(nil) do
diff --git a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex
index 11871375e..b10b27f06 100644
--- a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex
@@ -10,7 +10,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do
require Pleroma.Constants
defp check_by_actor_type(user), do: user.actor_type in ["Application", "Service"]
- defp check_by_nickname(user), do: Regex.match?(~r/bot@|ebooks@/i, user.nickname)
+ defp check_by_nickname(user), do: Regex.match?(~r/.bot@|ebooks@/i, user.nickname)
defp check_if_bot(user), do: check_by_actor_type(user) or check_by_nickname(user)
diff --git a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex
new file mode 100644
index 000000000..255910b2f
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex
@@ -0,0 +1,132 @@
+# Pleroma: A lightweight social networking server
+# Copyright ยฉ 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do
+ require Pleroma.Constants
+
+ alias Pleroma.Formatter
+ alias Pleroma.Object
+ alias Pleroma.User
+
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ defp do_extract({:a, attrs, _}, acc) do
+ if Enum.find(attrs, fn {name, value} ->
+ name == "class" && value in ["mention", "u-url mention", "mention u-url"]
+ end) do
+ href = Enum.find(attrs, fn {name, _} -> name == "href" end) |> elem(1)
+ acc ++ [href]
+ else
+ acc
+ end
+ end
+
+ defp do_extract({_, _, children}, acc) do
+ do_extract(children, acc)
+ end
+
+ defp do_extract(nodes, acc) when is_list(nodes) do
+ Enum.reduce(nodes, acc, fn node, acc -> do_extract(node, acc) end)
+ end
+
+ defp do_extract(_, acc), do: acc
+
+ defp extract_mention_uris_from_content(content) do
+ {:ok, tree} = :fast_html.decode(content, format: [:html_atoms])
+ do_extract(tree, [])
+ end
+
+ defp get_replied_to_user(%{"inReplyTo" => in_reply_to}) do
+ case Object.normalize(in_reply_to, fetch: false) do
+ %Object{data: %{"actor" => actor}} -> User.get_cached_by_ap_id(actor)
+ _ -> nil
+ end
+ end
+
+ defp get_replied_to_user(_object), do: nil
+
+ # Ensure the replied-to user is sorted to the left
+ defp sort_replied_user([%User{id: user_id} | _] = users, %User{id: user_id}), do: users
+
+ defp sort_replied_user(users, %User{id: user_id} = user) do
+ if Enum.find(users, fn u -> u.id == user_id end) do
+ users = Enum.reject(users, fn u -> u.id == user_id end)
+ [user | users]
+ else
+ users
+ end
+ end
+
+ defp sort_replied_user(users, _), do: users
+
+ # Drop constants and the actor's own AP ID
+ defp clean_recipients(recipients, object) do
+ Enum.reject(recipients, fn ap_id ->
+ ap_id in [
+ object["object"]["actor"],
+ Pleroma.Constants.as_public(),
+ Pleroma.Web.ActivityPub.Utils.as_local_public()
+ ]
+ end)
+ end
+
+ @impl true
+ def filter(
+ %{
+ "type" => "Create",
+ "object" => %{"type" => "Note", "to" => to, "inReplyTo" => in_reply_to}
+ } = object
+ )
+ when is_list(to) and is_binary(in_reply_to) do
+ # image-only posts from pleroma apparently reach this MRF without the content field
+ content = object["object"]["content"] || ""
+
+ # Get the replied-to user for sorting
+ replied_to_user = get_replied_to_user(object["object"])
+
+ mention_users =
+ to
+ |> clean_recipients(object)
+ |> Enum.map(&User.get_cached_by_ap_id/1)
+ |> Enum.reject(&is_nil/1)
+ |> sort_replied_user(replied_to_user)
+
+ explicitly_mentioned_uris = extract_mention_uris_from_content(content)
+
+ added_mentions =
+ Enum.reduce(mention_users, "", fn %User{ap_id: uri} = user, acc ->
+ unless uri in explicitly_mentioned_uris do
+ acc <> Formatter.mention_from_user(user, %{mentions_format: :compact}) <> " "
+ else
+ acc
+ end
+ end)
+
+ recipients_inline =
+ if added_mentions != "",
+ do: "<span class=\"recipients-inline\">#{added_mentions}</span>",
+ else: ""
+
+ content =
+ cond do
+ # For Markdown posts, insert the mentions inside the first <p> tag
+ recipients_inline != "" && String.starts_with?(content, "<p>") ->
+ "<p>" <> recipients_inline <> String.trim_leading(content, "<p>")
+
+ recipients_inline != "" ->
+ recipients_inline <> content
+
+ true ->
+ content
+ end
+
+ {:ok, put_in(object["object"]["content"], content)}
+ end
+
+ @impl true
+ def filter(object), do: {:ok, object}
+
+ @impl true
+ def describe, do: {:ok, %{}}
+end
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 344da19d3..d20d4591a 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -92,6 +92,11 @@ defmodule Pleroma.Web.ActivityPub.UserView do
%{}
end
+ birthday =
+ if user.show_birthday && user.birthday,
+ do: Date.to_iso8601(user.birthday),
+ else: nil
+
%{
"id" => user.ap_id,
"type" => user.actor_type,
@@ -116,7 +121,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
# Note: key name is indeed "discoverable" (not an error)
"discoverable" => user.is_discoverable,
"capabilities" => capabilities,
- "alsoKnownAs" => user.also_known_as
+ "alsoKnownAs" => user.also_known_as,
+ "vcard:bday" => birthday
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index f5304d7d6..03efa3c38 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -334,6 +334,42 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
}
end
+ def endorse_operation do
+ %Operation{
+ tags: ["Account actions"],
+ summary: "Endorse",
+ operationId: "AccountController.endorse",
+ security: [%{"oAuth" => ["follow", "write:accounts"]}],
+ description: "Addds the given account to endorsed accounts list.",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship),
+ 400 =>
+ Operation.response("Bad Request", "application/json", %Schema{
+ allOf: [ApiError],
+ title: "Unprocessable Entity",
+ example: %{
+ "error" => "You have already pinned the maximum number of users"
+ }
+ })
+ }
+ }
+ end
+
+ def unendorse_operation do
+ %Operation{
+ tags: ["Account actions"],
+ summary: "Unendorse",
+ operationId: "AccountController.unendorse",
+ security: [%{"oAuth" => ["follow", "write:accounts"]}],
+ description: "Removes the given account from endorsed accounts list.",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship)
+ }
+ }
+ end
+
def note_operation do
%Operation{
tags: ["Account actions"],
@@ -425,10 +461,10 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
tags: ["Retrieve account information"],
summary: "Endorsements",
operationId: "AccountController.endorsements",
- description: "Not implemented",
+ description: "Returns endorsed accounts",
security: [%{"oAuth" => ["read:accounts"]}],
responses: %{
- 200 => empty_array_response()
+ 200 => Operation.response("Array of Accounts", "application/json", array_of_accounts())
}
}
end
@@ -507,6 +543,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
type: :string,
nullable: true,
description: "Invite token required when the registrations aren't public"
+ },
+ birthday: %Schema{
+ type: :string,
+ nullable: true,
+ description: "User's birthday",
+ format: :date
}
},
example: %{
@@ -684,7 +726,18 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
description:
"Discovery (listing, indexing) of this account by external services (search bots etc.) is allowed."
},
- actor_type: ActorType
+ actor_type: ActorType,
+ birthday: %Schema{
+ type: :string,
+ nullable: true,
+ description: "User's birthday",
+ format: :date
+ },
+ show_birthday: %Schema{
+ allOf: [BooleanLike],
+ nullable: true,
+ description: "User's birthday will be visible"
+ }
},
example: %{
bot: false,
@@ -704,7 +757,9 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
allow_following_move: false,
also_known_as: ["https://foo.bar/users/foo"],
discoverable: false,
- actor_type: "Person"
+ actor_type: "Person",
+ show_birthday: false,
+ birthday: "2001-02-12"
}
}
end
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
index ad49f6426..23201a4ba 100644
--- a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
@@ -4,6 +4,8 @@
defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.AccountOperation
alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
@@ -62,6 +64,25 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
}
end
+ def endorsements_operation do
+ %Operation{
+ tags: ["Retrieve account information"],
+ summary: "Endorsements",
+ description: "Returns endorsed accounts",
+ operationId: "PleromaAPI.AccountController.endorsements",
+ parameters: [with_relationships_param(), id_param()],
+ responses: %{
+ 200 =>
+ Operation.response(
+ "Array of Accounts",
+ "application/json",
+ AccountOperation.array_of_accounts()
+ ),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
def subscribe_operation do
%Operation{
tags: ["Account actions"],
@@ -92,6 +113,34 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
}
end
+ def birthdays_operation do
+ %Operation{
+ tags: ["Retrieve account information"],
+ summary: "Birthday reminders",
+ description: "Birthday reminders about users you follow.",
+ operationId: "PleromaAPI.AccountController.birthdays",
+ parameters: [
+ Operation.parameter(
+ :day,
+ :query,
+ %Schema{type: :integer},
+ "Day of users' birthdays"
+ ),
+ Operation.parameter(
+ :month,
+ :query,
+ %Schema{type: :integer},
+ "Month of users' birthdays"
+ )
+ ],
+ security: [%{"oAuth" => ["read:accounts"]}],
+ responses: %{
+ 200 =>
+ Operation.response("Accounts", "application/json", AccountOperation.array_of_accounts())
+ }
+ }
+ end
+
defp id_param do
Operation.parameter(:id, :path, FlakeID, "Account ID",
example: "9umDrYheeY451cQnEe",
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index 548e70544..029c6f6cf 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -47,12 +47,14 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
description: "whether the user allows automatically follow moved following accounts"
},
background_image: %Schema{type: :string, nullable: true, format: :uri},
+ birthday: %Schema{type: :string, nullable: true, format: :date},
chat_token: %Schema{type: :string},
is_confirmed: %Schema{
type: :boolean,
description:
"whether the user account is waiting on email confirmation to be activated"
},
+ show_birthday: %Schema{type: :boolean, nullable: true},
hide_favorites: %Schema{type: :boolean},
hide_followers_count: %Schema{
type: :boolean,
@@ -202,7 +204,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
},
"settings_store" => %{
"pleroma-fe" => %{}
- }
+ },
+ "birthday" => "2001-02-12"
},
"source" => %{
"fields" => [],
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
index 6f685cb7b..2481e4e16 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -117,7 +117,8 @@ defmodule Pleroma.Web.CommonAPI do
def unfollow(follower, unfollowed) do
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed),
- {:ok, _subscription} <- User.unsubscribe(follower, unfollowed) do
+ {:ok, _subscription} <- User.unsubscribe(follower, unfollowed),
+ {:ok, _endorsement} <- User.unendorse(follower, unfollowed) do
{:ok, follower}
end
end
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index b4e3e37ae..451d7323a 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -112,7 +112,12 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
defp attachments(%{params: params} = draft) do
attachments = Utils.attachments_from_ids(params)
- %__MODULE__{draft | attachments: attachments}
+ draft = %__MODULE__{draft | attachments: attachments}
+
+ case Utils.validate_attachments_count(attachments) do
+ :ok -> draft
+ {:error, message} -> add_error(draft, message)
+ end
end
defp in_reply_to(%{params: %{in_reply_to_status_id: ""}} = draft), do: draft
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index b6feaf32a..5bba01cc4 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -492,4 +492,19 @@ defmodule Pleroma.Web.CommonAPI.Utils do
{:error, dgettext("errors", "The status is over the character limit")}
end
end
+
+ def validate_attachments_count([] = _attachments) do
+ :ok
+ end
+
+ def validate_attachments_count(attachments) do
+ limit = Config.get([:instance, :max_media_attachments])
+ count = length(attachments)
+
+ if count <= limit do
+ :ok
+ else
+ {:error, dgettext("errors", "Too many attachments")}
+ end
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index a307807a9..8e6d49168 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -57,7 +57,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
plug(
OAuthScopesPlug,
%{scopes: ["write:accounts"]}
- when action in [:update_credentials, :note]
+ when action in [:update_credentials, :note, :endorse, :unendorse]
)
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :lists)
@@ -84,7 +84,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
@relationship_actions [:follow, :unfollow]
- @needs_account ~W(followers following lists follow unfollow mute unmute block unblock note)a
+ @needs_account ~W(
+ followers following lists follow unfollow mute unmute block unblock note endorse unendorse
+ )a
plug(
RateLimiter,
@@ -189,7 +191,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
:skip_thread_containment,
:allow_following_move,
:also_known_as,
- :accepts_chat_messages
+ :accepts_chat_messages,
+ :show_birthday
]
|> Enum.reduce(%{}, fn key, acc ->
Maps.put_if_present(acc, key, params[key], &{:ok, Params.truthy_param?(&1)})
@@ -217,6 +220,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|> Maps.put_if_present(:is_locked, params[:locked])
# Note: param name is indeed :discoverable (not an error)
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
+ |> Maps.put_if_present(:birthday, params[:birthday])
# What happens here:
#
@@ -450,6 +454,24 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
end
end
+ @doc "POST /api/v1/accounts/:id/pin"
+ def endorse(%{assigns: %{user: endorser, account: endorsed}} = conn, _params) do
+ with {:ok, _user_relationships} <- User.endorse(endorser, endorsed) do
+ render(conn, "relationship.json", user: endorser, target: endorsed)
+ else
+ {:error, message} -> json_response(conn, :bad_request, %{error: message})
+ end
+ end
+
+ @doc "POST /api/v1/accounts/:id/unpin"
+ def unendorse(%{assigns: %{user: endorser, account: endorsed}} = conn, _params) do
+ with {:ok, _user_relationships} <- User.unendorse(endorser, endorsed) do
+ render(conn, "relationship.json", user: endorser, target: endorsed)
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+
@doc "POST /api/v1/follows"
def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do
case User.get_cached_by_nickname(uri) do
@@ -505,7 +527,20 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
end
@doc "GET /api/v1/endorsements"
- def endorsements(conn, params), do: MastodonAPIController.empty_array(conn, params)
+ def endorsements(%{assigns: %{user: user}} = conn, params) do
+ users =
+ user
+ |> User.endorsed_users_relation(_restrict_deactivated = true)
+ |> Pleroma.Repo.all()
+
+ conn
+ |> render("index.json",
+ users: users,
+ for: user,
+ as: :user,
+ embed_relationships: embed_relationships?(params)
+ )
+ end
@doc "GET /api/v1/identity_proofs"
def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params)
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 4b15b1635..1d78ced19 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -160,11 +160,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
target,
&User.muting_reblogs?(&1, &2)
),
- endorsed: false,
note:
UserNote.show(
reading_user,
target
+ ),
+ endorsed:
+ UserRelationship.exists?(
+ user_relationships,
+ :endorsement,
+ target,
+ reading_user,
+ &User.endorses?(&2, &1)
)
}
end
@@ -304,6 +311,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> maybe_put_unread_conversation_count(user, opts[:for])
|> maybe_put_unread_notification_count(user, opts[:for])
|> maybe_put_email_address(user, opts[:for])
+ |> maybe_show_birthday(user, opts[:for])
end
defp username_from_nickname(string) when is_binary(string) do
@@ -337,6 +345,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> 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([:source, :pleroma, :show_birthday], user.show_birthday)
end
defp maybe_put_settings(data, _, _, _), do: data
@@ -425,6 +434,20 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
defp maybe_put_email_address(data, _, _), do: data
+ defp maybe_show_birthday(data, %User{id: user_id} = user, %User{id: user_id}) do
+ data
+ |> Kernel.put_in([:pleroma, :birthday], user.birthday)
+ end
+
+ defp maybe_show_birthday(data, %User{show_birthday: true} = user, _) do
+ data
+ |> Kernel.put_in([:pleroma, :birthday], user.birthday)
+ end
+
+ defp maybe_show_birthday(data, _, _) do
+ data
+ end
+
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
defp image_url(_), do: nil
end
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index 8e657ee0f..23770f671 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -31,6 +31,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
approval_required: Keyword.get(instance, :account_approval_required),
# Extra (not present in Mastodon):
max_toot_chars: Keyword.get(instance, :limit),
+ max_media_attachments: Keyword.get(instance, :max_media_attachments),
poll_limits: Keyword.get(instance, :poll_limits),
upload_limit: Keyword.get(instance, :upload_limit),
avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit),
@@ -46,7 +47,9 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
federation: federation(),
fields_limits: fields_limits(),
post_formats: Config.get([:instance, :allowed_post_formats]),
- privileged_staff: Config.get([:instance, :privileged_staff])
+ privileged_staff: Config.get([:instance, :privileged_staff]),
+ birthday_required: Config.get([:instance, :birthday_required]),
+ birthday_min_age: Config.get([:instance, :birthday_min_age])
},
stats: %{mau: Pleroma.User.active_user_count()},
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
@@ -65,6 +68,9 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
"shareable_emoji_packs",
"multifetch",
"pleroma:api/v1/notifications:include_types_filter",
+ if Config.get([:activitypub, :blockers_visible]) do
+ "blockers_visible"
+ end,
if Config.get([:media_proxy, :enabled]) do
"media_proxy"
end,
diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
index 8e4d3e7f7..d78ebbe2e 100644
--- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
@@ -6,7 +6,12 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper,
- only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
+ only: [
+ json_response: 3,
+ add_link_headers: 2,
+ embed_relationships?: 1,
+ assign_account_by_id: 2
+ ]
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
@@ -40,9 +45,23 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
%{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites
)
+ plug(
+ OAuthScopesPlug,
+ %{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
+ when action == :endorsements
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read:accounts"]} when action == :birthdays
+ )
+
plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend)
- plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe])
+ plug(
+ :assign_account_by_id
+ when action in [:favourites, :endorsements, :subscribe, :unsubscribe]
+ )
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAccountOperation
@@ -90,6 +109,22 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
)
end
+ @doc "GET /api/v1/pleroma/accounts/:id/endorsements"
+ def endorsements(%{assigns: %{user: for_user, account: user}} = conn, params) do
+ users =
+ user
+ |> User.endorsed_users_relation(_restrict_deactivated = true)
+ |> Pleroma.Repo.all()
+
+ conn
+ |> render("index.json",
+ for: for_user,
+ users: users,
+ as: :user,
+ embed_relationships: embed_relationships?(params)
+ )
+ end
+
@doc "POST /api/v1/pleroma/accounts/:id/subscribe"
def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do
with {:ok, _subscription} <- User.subscribe(user, subscription_target) do
@@ -107,4 +142,18 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
{:error, message} -> json_response(conn, :forbidden, %{error: message})
end
end
+
+ @doc "GET /api/v1/pleroma/birthdays"
+ def birthdays(%{assigns: %{user: %User{} = user}} = conn, %{day: day, month: month} = _params) do
+ birthdays =
+ User.get_friends_birthdays_query(user, day, month)
+ |> Pleroma.Repo.all()
+
+ conn
+ |> render("index.json",
+ for: user,
+ users: birthdays,
+ as: :user
+ )
+ end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 0b1a59f35..9198d280b 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -440,6 +440,7 @@ defmodule Pleroma.Web.Router do
scope [] do
pipe_through(:api)
get("/accounts/:id/favourites", AccountController, :favourites)
+ get("/accounts/:id/endorsements", AccountController, :endorsements)
end
scope [] do
@@ -447,6 +448,8 @@ defmodule Pleroma.Web.Router do
post("/accounts/:id/subscribe", AccountController, :subscribe)
post("/accounts/:id/unsubscribe", AccountController, :unsubscribe)
+
+ get("/birthdays", AccountController, :birthdays)
end
post("/accounts/confirmation_resend", AccountController, :confirmation_resend)
@@ -486,6 +489,8 @@ defmodule Pleroma.Web.Router do
post("/accounts/:id/mute", AccountController, :mute)
post("/accounts/:id/unmute", AccountController, :unmute)
post("/accounts/:id/note", AccountController, :note)
+ post("/accounts/:id/pin", AccountController, :endorse)
+ post("/accounts/:id/unpin", AccountController, :unendorse)
get("/conversations", ConversationController, :index)
post("/conversations/:id/read", ConversationController, :mark_as_read)
diff --git a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
index 29ea7c5fb..27600253c 100644
--- a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
+++ b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
@@ -10,7 +10,7 @@
<%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
<div class="input">
<%= label f, :code, "Authentication code" %>
- <%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, pattern: "[0-9]*", spellcheck: false] %>
+ <%= text_input f, :code, [autocomplete: "one-time-code", autocorrect: "off", autocapitalize: "off", autofocus: true, pattern: "[0-9]*", spellcheck: false] %>
<%= hidden_input f, :mfa_token, value: @mfa_token %>
<%= hidden_input f, :state, value: @state %>
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
index 99f900fb7..3ac428b2f 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
@@ -12,11 +12,11 @@
<div class="input">
<%= label f, :nickname, "Nickname" %>
- <%= text_input f, :nickname, value: @nickname %>
+ <%= text_input f, :nickname, value: @nickname, autocomplete: "username" %>
</div>
<div class="input">
<%= label f, :email, "Email" %>
- <%= text_input f, :email, value: @email %>
+ <%= text_input f, :email, value: @email, autocomplete: "email" %>
</div>
<%= submit "Proceed as new user", name: "op", value: "register" %>
@@ -25,11 +25,11 @@
<div class="input">
<%= label f, :name, "Name or email" %>
- <%= text_input f, :name %>
+ <%= text_input f, :name, autocomplete: "username" %>
</div>
<div class="input">
<%= label f, :password, "Password" %>
- <%= password_input f, :password %>
+ <%= password_input f, :password, autocomplete: "password" %>
</div>
<%= submit "Proceed as existing user", name: "op", value: "connect" %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
index 181a9519a..d63da6c1d 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
@@ -35,7 +35,7 @@
<p>Choose carefully! You won't be able to change this later. You will be able to change your display name, though.</p>
<div class="input">
<%= label f, :nickname, "Pleroma Handle" %>
- <%= text_input f, :nickname, placeholder: "lain" %>
+ <%= text_input f, :nickname, placeholder: "lain", autocomplete: "username" %>
</div>
<%= hidden_input f, :name, value: @params["name"] %>
<%= hidden_input f, :password, value: @params["password"] %>
diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
index a8026fa9d..bc5fb28e3 100644
--- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
@@ -5,9 +5,9 @@
<p><%= @followee.nickname %></p>
<img height="128" width="128" src="<%= avatar_url(@followee) %>">
<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %>
-<%= text_input f, :name, placeholder: "Username", required: true %>
+<%= text_input f, :name, placeholder: "Username", required: true, autocomplete: "username" %>
<br>
-<%= password_input f, :password, placeholder: "Password", required: true %>
+<%= password_input f, :password, placeholder: "Password", required: true, autocomplete: "password" %>
<br>
<%= hidden_input f, :id, value: @followee.id %>
<%= submit "Authorize" %>
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index 76ca82d20..aa4dfb145 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -20,6 +20,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|> Map.put(:name, Map.get(params, :fullname, params[:username]))
|> Map.put(:password_confirmation, params[:password])
|> Map.put(:registration_reason, params[:reason])
+ |> Map.put(:birthday, params[:birthday])
if Pleroma.Config.get([:instance, :registrations_open]) do
create_user(params, opts)