diff options
Diffstat (limited to 'lib')
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) |