diff --git a/assets/sockchat-smilies/attachments_cunt-png.63843.png b/assets/sockchat-smilies/attachments_cunt-png.63843.png new file mode 100644 index 0000000..f4df996 Binary files /dev/null and b/assets/sockchat-smilies/attachments_cunt-png.63843.png differ diff --git a/assets/sockchat-smilies/attachments_felted-gif.6203542.gif b/assets/sockchat-smilies/attachments_felted-gif.6203542.gif new file mode 100644 index 0000000..5a8d9bc Binary files /dev/null and b/assets/sockchat-smilies/attachments_felted-gif.6203542.gif differ diff --git a/assets/sockchat-smilies/attachments_nitenite-gif.6203539.gif b/assets/sockchat-smilies/attachments_nitenite-gif.6203539.gif new file mode 100644 index 0000000..74ad1ee Binary files /dev/null and b/assets/sockchat-smilies/attachments_nitenite-gif.6203539.gif differ diff --git a/assets/sockchat-smilies/attachments_ramona-ralph-png.6203404.png b/assets/sockchat-smilies/attachments_ramona-ralph-png.6203404.png new file mode 100644 index 0000000..15bdb64 Binary files /dev/null and b/assets/sockchat-smilies/attachments_ramona-ralph-png.6203404.png differ diff --git a/assets/sockchat-smilies/styles_custom_emoji_Achievement.svg b/assets/sockchat-smilies/styles_custom_emoji_Achievement.svg new file mode 100644 index 0000000..91fe695 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emoji_Achievement.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/sockchat-smilies/styles_custom_emoji_Agree.svg b/assets/sockchat-smilies/styles_custom_emoji_Agree.svg new file mode 100644 index 0000000..7c78bbc --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emoji_Agree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/sockchat-smilies/styles_custom_emoji_Disagree.svg b/assets/sockchat-smilies/styles_custom_emoji_Disagree.svg new file mode 100644 index 0000000..adaa528 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emoji_Disagree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/sockchat-smilies/styles_custom_emoji_Dislike.svg b/assets/sockchat-smilies/styles_custom_emoji_Dislike.svg new file mode 100644 index 0000000..78e345c --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emoji_Dislike.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/sockchat-smilies/styles_custom_emoji_Drink.svg b/assets/sockchat-smilies/styles_custom_emoji_Drink.svg new file mode 100644 index 0000000..13a4e4e --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emoji_Drink.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/sockchat-smilies/styles_custom_emoji_Feels.svg b/assets/sockchat-smilies/styles_custom_emoji_Feels.svg new file mode 100644 index 0000000..bff9dc9 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emoji_Feels.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/sockchat-smilies/styles_custom_emoji_Informative.svg b/assets/sockchat-smilies/styles_custom_emoji_Informative.svg new file mode 100644 index 0000000..080fb4f --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emoji_Informative.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/sockchat-smilies/styles_custom_emoji_Like.svg b/assets/sockchat-smilies/styles_custom_emoji_Like.svg new file mode 100644 index 0000000..395031c --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emoji_Like.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/sockchat-smilies/styles_custom_emoji_Lovequest_200_202025.svg b/assets/sockchat-smilies/styles_custom_emoji_Lovequest_200_202025.svg new file mode 100644 index 0000000..8dd0843 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emoji_Lovequest_200_202025.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/sockchat-smilies/styles_custom_emoji_Lovequest_20100_202025.svg b/assets/sockchat-smilies/styles_custom_emoji_Lovequest_20100_202025.svg new file mode 100644 index 0000000..cfe027b --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emoji_Lovequest_20100_202025.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emoji_Lunacy.svg b/assets/sockchat-smilies/styles_custom_emoji_Lunacy.svg new file mode 100644 index 0000000..6078b11 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emoji_Lunacy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/sockchat-smilies/styles_custom_emoji_Moai.svg b/assets/sockchat-smilies/styles_custom_emoji_Moai.svg new file mode 100644 index 0000000..d8f1583 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emoji_Moai.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/sockchat-smilies/styles_custom_emoji_Mutt_20Love.svg b/assets/sockchat-smilies/styles_custom_emoji_Mutt_20Love.svg new file mode 100644 index 0000000..0363e06 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emoji_Mutt_20Love.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/sockchat-smilies/styles_custom_emoji_Optimistic.svg b/assets/sockchat-smilies/styles_custom_emoji_Optimistic.svg new file mode 100644 index 0000000..1974301 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emoji_Optimistic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/sockchat-smilies/styles_custom_emoji_Semper_20Fidelis.svg b/assets/sockchat-smilies/styles_custom_emoji_Semper_20Fidelis.svg new file mode 100644 index 0000000..00129df --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emoji_Semper_20Fidelis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/sockchat-smilies/styles_custom_emoji_Winner.svg b/assets/sockchat-smilies/styles_custom_emoji_Winner.svg new file mode 100644 index 0000000..14cc24c --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emoji_Winner.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/sockchat-smilies/styles_custom_emotes_ChantalSmug.png b/assets/sockchat-smilies/styles_custom_emotes_ChantalSmug.png new file mode 100644 index 0000000..4abddb5 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_ChantalSmug.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_Grab.png b/assets/sockchat-smilies/styles_custom_emotes_Grab.png new file mode 100644 index 0000000..e73b902 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_Grab.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_applecat.webp b/assets/sockchat-smilies/styles_custom_emotes_applecat.webp new file mode 100644 index 0000000..678b401 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_applecat.webp differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_bj.png b/assets/sockchat-smilies/styles_custom_emotes_bj.png new file mode 100644 index 0000000..0cdc968 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_bj.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_bmj_juicy.gif b/assets/sockchat-smilies/styles_custom_emotes_bmj_juicy.gif new file mode 100644 index 0000000..922c10a Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_bmj_juicy.gif differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_bmj_loss.png b/assets/sockchat-smilies/styles_custom_emotes_bmj_loss.png new file mode 100644 index 0000000..1f30d60 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_bmj_loss.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_bmj_ross_hq.png b/assets/sockchat-smilies/styles_custom_emotes_bmj_ross_hq.png new file mode 100644 index 0000000..64cd425 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_bmj_ross_hq.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_bogged.png b/assets/sockchat-smilies/styles_custom_emotes_bogged.png new file mode 100644 index 0000000..3542ba4 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_bogged.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_cans.gif b/assets/sockchat-smilies/styles_custom_emotes_cans.gif new file mode 100644 index 0000000..63bb382 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_cans.gif differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_chicken.gif b/assets/sockchat-smilies/styles_custom_emotes_chicken.gif new file mode 100644 index 0000000..55fd187 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_chicken.gif differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_crocodile.png b/assets/sockchat-smilies/styles_custom_emotes_crocodile.png new file mode 100644 index 0000000..c81faae Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_crocodile.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_cuck.png b/assets/sockchat-smilies/styles_custom_emotes_cuck.png new file mode 100644 index 0000000..25ee477 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_cuck.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_cunning-pepe.png b/assets/sockchat-smilies/styles_custom_emotes_cunning-pepe.png new file mode 100644 index 0000000..486813b Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_cunning-pepe.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_diddler2.png b/assets/sockchat-smilies/styles_custom_emotes_diddler2.png new file mode 100644 index 0000000..2a526da Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_diddler2.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_dienull.gif b/assets/sockchat-smilies/styles_custom_emotes_dienull.gif new file mode 100644 index 0000000..fe79654 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_dienull.gif differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_dog.gif b/assets/sockchat-smilies/styles_custom_emotes_dog.gif new file mode 100644 index 0000000..ae90f18 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_dog.gif differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_dsp-stress.png b/assets/sockchat-smilies/styles_custom_emotes_dsp-stress.png new file mode 100644 index 0000000..73858f9 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_dsp-stress.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_dspcoom.png b/assets/sockchat-smilies/styles_custom_emotes_dspcoom.png new file mode 100644 index 0000000..33ff6d7 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_dspcoom.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_eggplant.png b/assets/sockchat-smilies/styles_custom_emotes_eggplant.png new file mode 100644 index 0000000..f4398b9 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_eggplant.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_fatpat.png b/assets/sockchat-smilies/styles_custom_emotes_fatpat.png new file mode 100644 index 0000000..f6ce88d Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_fatpat.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_filthy.gif b/assets/sockchat-smilies/styles_custom_emotes_filthy.gif new file mode 100644 index 0000000..9cd623d Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_filthy.gif differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_gator.webp b/assets/sockchat-smilies/styles_custom_emotes_gator.webp new file mode 100644 index 0000000..a87ea1b Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_gator.webp differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_gay.png b/assets/sockchat-smilies/styles_custom_emotes_gay.png new file mode 100644 index 0000000..6ed53b7 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_gay.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_ghost-laugh.gif b/assets/sockchat-smilies/styles_custom_emotes_ghost-laugh.gif new file mode 100644 index 0000000..cb7d7b6 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_ghost-laugh.gif differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_ghost-woo.gif b/assets/sockchat-smilies/styles_custom_emotes_ghost-woo.gif new file mode 100644 index 0000000..d6dd9cb Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_ghost-woo.gif differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_ghost.png b/assets/sockchat-smilies/styles_custom_emotes_ghost.png new file mode 100644 index 0000000..6e7a33e Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_ghost.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_glaive.png b/assets/sockchat-smilies/styles_custom_emotes_glaive.png new file mode 100644 index 0000000..94fc54f Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_glaive.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_gold.png b/assets/sockchat-smilies/styles_custom_emotes_gold.png new file mode 100644 index 0000000..73e88eb Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_gold.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_gunt.gif b/assets/sockchat-smilies/styles_custom_emotes_gunt.gif new file mode 100644 index 0000000..8a4dab4 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_gunt.gif differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_hambone.png b/assets/sockchat-smilies/styles_custom_emotes_hambone.png new file mode 100644 index 0000000..e7e3095 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_hambone.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_icon_wall.gif b/assets/sockchat-smilies/styles_custom_emotes_icon_wall.gif new file mode 100644 index 0000000..4c28c3e Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_icon_wall.gif differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_jace_0XKIXBh.png b/assets/sockchat-smilies/styles_custom_emotes_jace_0XKIXBh.png new file mode 100644 index 0000000..82f6fec Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_jace_0XKIXBh.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_jace_2dDorQI.png b/assets/sockchat-smilies/styles_custom_emotes_jace_2dDorQI.png new file mode 100644 index 0000000..99273bc Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_jace_2dDorQI.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_jace_7wA9pXj.png b/assets/sockchat-smilies/styles_custom_emotes_jace_7wA9pXj.png new file mode 100644 index 0000000..aa00e67 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_jace_7wA9pXj.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_jace_CHJMi6Q.png b/assets/sockchat-smilies/styles_custom_emotes_jace_CHJMi6Q.png new file mode 100644 index 0000000..b0121d7 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_jace_CHJMi6Q.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_jace_NYuMHTI.png b/assets/sockchat-smilies/styles_custom_emotes_jace_NYuMHTI.png new file mode 100644 index 0000000..03ea619 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_jace_NYuMHTI.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_jace_Of1Yvii.png b/assets/sockchat-smilies/styles_custom_emotes_jace_Of1Yvii.png new file mode 100644 index 0000000..675c0c0 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_jace_Of1Yvii.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_jace_Uw8NCT5.png b/assets/sockchat-smilies/styles_custom_emotes_jace_Uw8NCT5.png new file mode 100644 index 0000000..a01264c Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_jace_Uw8NCT5.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_jace_a6181SW.png b/assets/sockchat-smilies/styles_custom_emotes_jace_a6181SW.png new file mode 100644 index 0000000..e304de5 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_jace_a6181SW.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_jace_cLeOgy3.png b/assets/sockchat-smilies/styles_custom_emotes_jace_cLeOgy3.png new file mode 100644 index 0000000..c4ebb80 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_jace_cLeOgy3.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_jace_ifPazCJ.png b/assets/sockchat-smilies/styles_custom_emotes_jace_ifPazCJ.png new file mode 100644 index 0000000..2c2af44 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_jace_ifPazCJ.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_jace_oLzPk8e.png b/assets/sockchat-smilies/styles_custom_emotes_jace_oLzPk8e.png new file mode 100644 index 0000000..6d62578 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_jace_oLzPk8e.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_jace_uZd19Bc.png b/assets/sockchat-smilies/styles_custom_emotes_jace_uZd19Bc.png new file mode 100644 index 0000000..0cb02b2 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_jace_uZd19Bc.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_jace_ylo9W2m.png b/assets/sockchat-smilies/styles_custom_emotes_jace_ylo9W2m.png new file mode 100644 index 0000000..df95c04 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_jace_ylo9W2m.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_johnny-walker-blue-label-oh-yea.png b/assets/sockchat-smilies/styles_custom_emotes_johnny-walker-blue-label-oh-yea.png new file mode 100644 index 0000000..7e9bb9d Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_johnny-walker-blue-label-oh-yea.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_male.png b/assets/sockchat-smilies/styles_custom_emotes_male.png new file mode 100644 index 0000000..e2c4c07 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_male.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_mortal-wombat.png b/assets/sockchat-smilies/styles_custom_emotes_mortal-wombat.png new file mode 100644 index 0000000..1714162 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_mortal-wombat.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_notice-me.gif b/assets/sockchat-smilies/styles_custom_emotes_notice-me.gif new file mode 100644 index 0000000..3423994 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_notice-me.gif differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_ow.gif b/assets/sockchat-smilies/styles_custom_emotes_ow.gif new file mode 100644 index 0000000..d0e1a23 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_ow.gif differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_panda.gif b/assets/sockchat-smilies/styles_custom_emotes_panda.gif new file mode 100644 index 0000000..59ac688 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_panda.gif differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_pinetar.png b/assets/sockchat-smilies/styles_custom_emotes_pinetar.png new file mode 100644 index 0000000..d8af16b Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_pinetar.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_post-reply.png b/assets/sockchat-smilies/styles_custom_emotes_post-reply.png new file mode 100644 index 0000000..0667249 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_post-reply.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_reality.png b/assets/sockchat-smilies/styles_custom_emotes_reality.png new file mode 100644 index 0000000..f149448 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_reality.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_rekieta_really.webp b/assets/sockchat-smilies/styles_custom_emotes_rekieta_really.webp new file mode 100644 index 0000000..09efd94 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_rekieta_really.webp differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_salt.gif b/assets/sockchat-smilies/styles_custom_emotes_salt.gif new file mode 100644 index 0000000..cadc1f0 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_salt.gif differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_shit-eating.jpg b/assets/sockchat-smilies/styles_custom_emotes_shit-eating.jpg new file mode 100644 index 0000000..37a9b0e Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_shit-eating.jpg differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_something-smug.gif b/assets/sockchat-smilies/styles_custom_emotes_something-smug.gif new file mode 100644 index 0000000..c806130 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_something-smug.gif differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_spudking.png b/assets/sockchat-smilies/styles_custom_emotes_spudking.png new file mode 100644 index 0000000..daf14c5 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_spudking.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_striker.webp b/assets/sockchat-smilies/styles_custom_emotes_striker.webp new file mode 100644 index 0000000..e1d3ef7 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_striker.webp differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_suffering.png b/assets/sockchat-smilies/styles_custom_emotes_suffering.png new file mode 100644 index 0000000..08577db Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_suffering.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-bigsmile.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-bigsmile.svg new file mode 100644 index 0000000..b2c1a90 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-bigsmile.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-christine.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-christine.svg new file mode 100644 index 0000000..012ddb0 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-christine.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-confused.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-confused.svg new file mode 100644 index 0000000..723c099 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-confused.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-cool.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-cool.svg new file mode 100644 index 0000000..4d34176 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-cool.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-cringe.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-cringe.svg new file mode 100644 index 0000000..df62b2c --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-cringe.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-cryblood.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-cryblood.svg new file mode 100644 index 0000000..bbc633c --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-cryblood.svg @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-eek.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-eek.svg new file mode 100644 index 0000000..dc2f5e4 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-eek.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-embarassed.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-embarassed.svg new file mode 100644 index 0000000..f0b2bd1 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-embarassed.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-evil.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-evil.svg new file mode 100644 index 0000000..841db0c --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-evil.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-laugh.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-laugh.svg new file mode 100644 index 0000000..87b5068 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-laugh.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-lit.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-lit.svg new file mode 100644 index 0000000..b489a40 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-lit.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-mad.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-mad.svg new file mode 100644 index 0000000..53cf0d4 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-mad.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-nerd.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-nerd.svg new file mode 100644 index 0000000..bab5596 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-nerd.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-neutral.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-neutral.svg new file mode 100644 index 0000000..97cd5df --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-neutral.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-punished.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-punished.svg new file mode 100644 index 0000000..c39af8c --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-punished.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-razz.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-razz.svg new file mode 100644 index 0000000..51a56a5 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-razz.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-roll.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-roll.svg new file mode 100644 index 0000000..4b720ae --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-roll.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-sad.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-sad.svg new file mode 100644 index 0000000..0f07ba8 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-sad.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-sigh.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-sigh.svg new file mode 100644 index 0000000..fdf0f61 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-sigh.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-slumber.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-slumber.svg new file mode 100644 index 0000000..c34a50b --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-slumber.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-smile.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-smile.svg new file mode 100644 index 0000000..a681e17 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-smile.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-surprised.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-surprised.svg new file mode 100644 index 0000000..d9b164a --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-surprised.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-tears.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-tears.svg new file mode 100644 index 0000000..60ab69c --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-tears.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-twisted.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-twisted.svg new file mode 100644 index 0000000..53648b3 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-twisted.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-unholy.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-unholy.svg new file mode 100644 index 0000000..4b3c0f7 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-unholy.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_chris-wink.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-wink.svg new file mode 100644 index 0000000..3385fd1 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_chris-wink.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_exclamation.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_exclamation.svg new file mode 100644 index 0000000..b9d6373 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_exclamation.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_svg_question.svg b/assets/sockchat-smilies/styles_custom_emotes_svg_question.svg new file mode 100644 index 0000000..b0fc679 --- /dev/null +++ b/assets/sockchat-smilies/styles_custom_emotes_svg_question.svg @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/assets/sockchat-smilies/styles_custom_emotes_thinking-face_1f914.png b/assets/sockchat-smilies/styles_custom_emotes_thinking-face_1f914.png new file mode 100644 index 0000000..61ed73b Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_thinking-face_1f914.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_trump.png b/assets/sockchat-smilies/styles_custom_emotes_trump.png new file mode 100644 index 0000000..86295cc Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_trump.png differ diff --git a/assets/sockchat-smilies/styles_custom_emotes_waifu-sad.png b/assets/sockchat-smilies/styles_custom_emotes_waifu-sad.png new file mode 100644 index 0000000..9efbeb8 Binary files /dev/null and b/assets/sockchat-smilies/styles_custom_emotes_waifu-sad.png differ diff --git a/assets/sockchat-smilies/styles_dark_ratings_press_1.png b/assets/sockchat-smilies/styles_dark_ratings_press_1.png new file mode 100644 index 0000000..0c12009 Binary files /dev/null and b/assets/sockchat-smilies/styles_dark_ratings_press_1.png differ diff --git a/assets/sockchat-smilies/styles_dark_ratings_press_2.png b/assets/sockchat-smilies/styles_dark_ratings_press_2.png new file mode 100644 index 0000000..dbc1b81 Binary files /dev/null and b/assets/sockchat-smilies/styles_dark_ratings_press_2.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_alog.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_alog.gif new file mode 100644 index 0000000..af9645c Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_alog.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_anna.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_anna.gif new file mode 100644 index 0000000..8ac717f Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_anna.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_asperchu.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_asperchu.png new file mode 100644 index 0000000..7051221 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_asperchu.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_aug_left.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_aug_left.png new file mode 100644 index 0000000..e2b1041 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_aug_left.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_aug_right.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_aug_right.png new file mode 100644 index 0000000..7bc4f87 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_aug_right.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_augh_yeah.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_augh_yeah.png new file mode 100644 index 0000000..64482ec Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_augh_yeah.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_billdozer.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_billdozer.gif new file mode 100644 index 0000000..281952a Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_billdozer.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_boozemythos.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_boozemythos.png new file mode 100644 index 0000000..8bc0d0b Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_boozemythos.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_bork.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_bork.png new file mode 100644 index 0000000..b1f24a0 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_bork.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_briefs.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_briefs.gif new file mode 100644 index 0000000..94414a1 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_briefs.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_brooklynbailiff.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_brooklynbailiff.gif new file mode 100644 index 0000000..6e7f759 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_brooklynbailiff.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_catparty.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_catparty.gif new file mode 100644 index 0000000..8b64b8b Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_catparty.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_champthom.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_champthom.gif new file mode 100644 index 0000000..af68ebe Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_champthom.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_coffee.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_coffee.png new file mode 100644 index 0000000..c94eadd Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_coffee.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_coffee_left.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_coffee_left.png new file mode 100644 index 0000000..8eef0f3 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_coffee_left.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_coster.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_coster.png new file mode 100644 index 0000000..eeadde7 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_coster.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_deagle.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_deagle.png new file mode 100644 index 0000000..b800268 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_deagle.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_deagle_left.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_deagle_left.png new file mode 100644 index 0000000..80dcbfd Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_deagle_left.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_doge_wow.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_doge_wow.gif new file mode 100644 index 0000000..e756699 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_doge_wow.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_fapcup.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_fapcup.gif new file mode 100644 index 0000000..ac20736 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_fapcup.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_freecell.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_freecell.gif new file mode 100644 index 0000000..384e973 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_freecell.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_grilledcheese.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_grilledcheese.gif new file mode 100644 index 0000000..15a5c3f Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_grilledcheese.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_horse_face.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_horse_face.gif new file mode 100644 index 0000000..58f165f Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_horse_face.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_hulk.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_hulk.png new file mode 100644 index 0000000..f916fd9 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_hulk.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_jace.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_jace.png new file mode 100644 index 0000000..c6020b6 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_jace.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_julay.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_julay.png new file mode 100644 index 0000000..770820d Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_julay.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_katsukitty.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_katsukitty.gif new file mode 100644 index 0000000..c5165d3 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_katsukitty.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_knife.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_knife.gif new file mode 100644 index 0000000..5292d15 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_knife.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_krystal.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_krystal.png new file mode 100644 index 0000000..d6a2944 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_krystal.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_left.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_left.gif new file mode 100644 index 0000000..3867f2e Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_left.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_medallion.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_medallion.gif new file mode 100644 index 0000000..a8fee57 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_medallion.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_melchett.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_melchett.gif new file mode 100644 index 0000000..085dc94 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_melchett.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_mythos.jpg b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_mythos.jpg new file mode 100644 index 0000000..f7e9c32 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_mythos.jpg differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_neckbeard.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_neckbeard.gif new file mode 100644 index 0000000..2a7d1c7 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_neckbeard.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_nice.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_nice.gif new file mode 100644 index 0000000..b18656e Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_nice.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_paulblart.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_paulblart.png new file mode 100644 index 0000000..c6dbfea Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_paulblart.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_pickle.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_pickle.gif new file mode 100644 index 0000000..e11f815 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_pickle.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_pickleman.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_pickleman.gif new file mode 100644 index 0000000..917864d Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_pickleman.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_poll.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_poll.png new file mode 100644 index 0000000..de7d468 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_poll.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_popcorn.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_popcorn.gif new file mode 100644 index 0000000..05a4a21 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_popcorn.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_qsand.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_qsand.gif new file mode 100644 index 0000000..f9f0535 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_qsand.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_ruse.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_ruse.png new file mode 100644 index 0000000..28dc0a7 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_ruse.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_sighduck.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_sighduck.gif new file mode 100644 index 0000000..f36932b Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_sighduck.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_silentprincess.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_silentprincess.png new file mode 100644 index 0000000..87fbb12 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_silentprincess.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_slayer.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_slayer.png new file mode 100644 index 0000000..9a02313 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_slayer.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_smokedaddy.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_smokedaddy.gif new file mode 100644 index 0000000..ca55f28 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_smokedaddy.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_snorlax.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_snorlax.gif new file mode 100644 index 0000000..717214c Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_snorlax.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_snowflake.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_snowflake.gif new file mode 100644 index 0000000..3cdedb6 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_snowflake.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_sonichu.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_sonichu.png new file mode 100644 index 0000000..aeff46c Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_sonichu.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_squirtle.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_squirtle.gif new file mode 100644 index 0000000..7bb93b4 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_squirtle.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_story.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_story.gif new file mode 100644 index 0000000..95dcf89 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_story.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_stressed.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_stressed.png new file mode 100644 index 0000000..235d230 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_stressed.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_sub.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_sub.png new file mode 100644 index 0000000..02f39f3 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_sub.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_sunshine_goodness.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_sunshine_goodness.png new file mode 100644 index 0000000..7598fdd Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_sunshine_goodness.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_tomgirl.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_tomgirl.gif new file mode 100644 index 0000000..6104f9b Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_tomgirl.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_troll.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_troll.gif new file mode 100644 index 0000000..a9cd16d Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_troll.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_tugboat.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_tugboat.gif new file mode 100644 index 0000000..cbdff78 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_tugboat.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_tyce.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_tyce.png new file mode 100644 index 0000000..ed192ec Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_tyce.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_tyced.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_tyced.png new file mode 100644 index 0000000..24fea2a Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_tyced.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_waifu.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_waifu.png new file mode 100644 index 0000000..90d8517 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_waifu.png differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_ween.gif b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_ween.gif new file mode 100644 index 0000000..a756d3e Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_icon_ween.gif differ diff --git a/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_snakesvsplanes.png b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_snakesvsplanes.png new file mode 100644 index 0000000..ffc2d42 Binary files /dev/null and b/assets/sockchat-smilies/styles_default_xenforo_smilies_phpbb_snakesvsplanes.png differ diff --git a/chatd-rs/chatd/src/backend/discord.rs b/chatd-rs/chatd/src/backend/discord.rs index 1abe990..6acc226 100644 --- a/chatd-rs/chatd/src/backend/discord.rs +++ b/chatd-rs/chatd/src/backend/discord.rs @@ -702,12 +702,14 @@ fn extract_reactions(msg: &Value) -> Vec { .filter_map(|r| { let count = r["count"].as_i64()?; let name = r["emoji"]["name"].as_str().unwrap_or("?"); + let is_custom = r["emoji"]["id"].as_str().is_some(); let emoji = match r["emoji"]["id"].as_str() { Some(id) => format!("<:{name}:{id}>"), None => name.to_string(), }; let me = r["me"].as_bool().unwrap_or(false); - Some(Reaction { emoji, count, me }) + let animated = is_custom && r["emoji"]["animated"].as_bool().unwrap_or(false); + Some(Reaction { emoji, count, me, animated }) }) .collect() } @@ -1196,3 +1198,60 @@ pub async fn delete_message(state: &AppState, buffer_id: &str, token: &str, msg_ } Ok(()) } + +/// Discord's reaction endpoint wants a custom emoji as `name:id` (no +/// angle brackets, no leading `a:` animated marker), but every reaction +/// this app already tracks (extract_reactions above, the live +/// MESSAGE_REACTION_ADD/REMOVE handling) stores it wrapped as `<:name:id>` +/// - that's the one place besides here needing the raw form, so it's +/// unwrapped here rather than changing the stored shape everywhere else. +/// A plain Unicode emoji (no wrapper) passes through unchanged. +fn reaction_path_segment(emoji: &str) -> &str { + emoji.strip_prefix("<:").or_else(|| emoji.strip_prefix("')).unwrap_or(emoji) +} + +/// PUT/DELETE .../messages/{id}/reactions/{emoji}/@me - adding or +/// removing *our own* reaction (Discord's reaction endpoints are +/// per-reactor; there's no "set the count directly" concept). Built via +/// `Url::path_segments_mut` rather than hand-rolled string formatting so +/// the emoji segment - raw Unicode bytes for a standard emoji, a `:` +/// inside a custom one - gets properly percent-encoded rather than +/// landing in the URL unescaped. +pub async fn toggle_reaction(state: &AppState, buffer_id: &str, token: &str, msg_id: &str, emoji: &str, add: bool) -> Result<()> { + let channel_id = state + .runtime + .get_discord_channel(buffer_id) + .ok_or_else(|| anyhow!("no known Discord channel for this buffer"))?; + let mut url = url::Url::parse(&format!("{API_BASE}/channels/{channel_id}/messages/{msg_id}/reactions")).context("building reaction URL")?; + url.path_segments_mut().map_err(|_| anyhow!("reaction URL cannot be a base"))?.push(reaction_path_segment(emoji)).push("@me"); + + let client = http_client(); + let req = if add { client.put(url) } else { client.delete(url) }; + let resp = req.header("Authorization", token).send().await.context("toggling Discord reaction")?; + if !resp.status().is_success() { + let status = resp.status(); + let text = resp.text().await.unwrap_or_default(); + bail!("Discord API error {status}: {text}"); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::reaction_path_segment; + + #[test] + fn unwraps_a_static_custom_emoji() { + assert_eq!(reaction_path_segment("<:pepege:123456789>"), "pepege:123456789"); + } + + #[test] + fn unwraps_an_animated_custom_emoji() { + assert_eq!(reaction_path_segment(""), "vibing:987654321"); + } + + #[test] + fn leaves_a_plain_unicode_emoji_unchanged() { + assert_eq!(reaction_path_segment("🔥"), "🔥"); + } +} diff --git a/chatd-rs/chatd/src/backend/sockchat/mod.rs b/chatd-rs/chatd/src/backend/sockchat/mod.rs index 14b8063..df78352 100644 --- a/chatd-rs/chatd/src/backend/sockchat/mod.rs +++ b/chatd-rs/chatd/src/backend/sockchat/mod.rs @@ -18,6 +18,7 @@ pub mod form; pub mod http; pub mod pow; pub mod protocol; +pub mod smilies; pub mod totp; use crate::accounts::{SockChatAccountConfig, SockChatRoom}; @@ -1098,6 +1099,25 @@ mod tests { assert_eq!(guess_postimg_content_type("clip.mp4"), None); assert_eq!(guess_postimg_content_type("noext"), None); } + + #[test] + fn smilie_bundled_files_are_unique_across_the_table() { + use std::collections::HashSet; + let names: HashSet<&str> = super::smilies::SMILIES.iter().map(|s| s.file).collect(); + assert_eq!(names.len(), super::smilies::SMILIES.len(), "two distinct smilies point at the same bundled asset file"); + } + + // Catches a typo'd filename or a missing/forgotten `git add` for a + // bundled asset at test time rather than as a silent broken image the + // first time someone actually opens the emoji picker or hits that + // shortcode in a live message. + #[test] + fn smilie_bundled_files_all_exist_on_disk() { + let assets_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../assets/sockchat-smilies"); + for s in super::smilies::SMILIES { + assert!(assets_dir.join(s.file).is_file(), "missing bundled asset for {:?}: {}", s.label, s.file); + } + } } #[cfg(test)] diff --git a/chatd-rs/chatd/src/backend/sockchat/smilies.rs b/chatd-rs/chatd/src/backend/sockchat/smilies.rs new file mode 100644 index 0000000..5578162 --- /dev/null +++ b/chatd-rs/chatd/src/backend/sockchat/smilies.rs @@ -0,0 +1,203 @@ +//! Kiwi Farms' site-wide smiley/emote table, scraped from https://kiwifarms.st/help/smilies/ +//! (a live browser session, since the reference page lazy-loads several rows via +//! CSS background-image rather than a plain , and the anonymous JSON +//! upload/API endpoints this project already reverse-engineers elsewhere don't cover +//! this at all - there's no public API, just the rendered page itself). +//! +//! The images themselves are bundled with the plugin under assets/sockchat-smilies/ +//! (see the frontend's own resolution of that directory via PluginService. +//! getPluginPath, ChatView.qml's fetchSockchatSmilies) rather than fetched from +//! kiwifarms.st at runtime - deliberate: this table is static and small (~1.8MB +//! total), so there's no reason to depend on a live Tor-routed fetch (and its +//! failure modes) just to show something this project already has a permanent, +//! correct copy of. + +pub struct Smilie { + pub label: &'static str, + /// Filename within assets/sockchat-smilies/ (relative to the plugin's own + /// root directory, not this crate's) - not a URL or a path on disk chatd + /// itself resolves; the frontend builds the actual file:// URL. + pub file: &'static str, + /// Every shortcode that triggers this smilie (site rendering treats them as + /// interchangeable aliases of the same image) - confirmed none collide across + /// the full 175-entry table. + pub aliases: &'static [&'static str], +} + +pub const SMILIES: &[Smilie] = &[ + Smilie { label: "Smile", file: "styles_custom_emotes_svg_chris-smile.svg", aliases: &[":)", ":happy:", ":smile:"] }, + Smilie { label: "Big Grin", file: "styles_custom_emotes_svg_chris-bigsmile.svg", aliases: &[":biggrin:", ":D", ":-D"] }, + Smilie { label: "Wink", file: "styles_custom_emotes_svg_chris-wink.svg", aliases: &[";)", ";-)", ":wink:"] }, + Smilie { label: "Lol", file: "styles_custom_emotes_svg_chris-laugh.svg", aliases: &[":lol:"] }, + Smilie { label: "Lit", file: "styles_custom_emotes_svg_chris-lit.svg", aliases: &[":lit:"] }, + Smilie { label: "Razz", file: "styles_custom_emotes_svg_chris-razz.svg", aliases: &[":P", ":razz:"] }, + Smilie { label: "Stress Sigh", file: "styles_custom_emotes_svg_chris-sigh.svg", aliases: &["*sigh*", "*SIGH*", ":sigh:"] }, + Smilie { label: "Rolling Eyes", file: "styles_custom_emotes_svg_chris-roll.svg", aliases: &[":roll:"] }, + Smilie { label: "Confused", file: "styles_custom_emotes_svg_chris-confused.svg", aliases: &[":\\", ":confused:"] }, + Smilie { label: "Surprised", file: "styles_custom_emotes_svg_chris-surprised.svg", aliases: &[":O", ":surprised:"] }, + Smilie { label: "Embarassed", file: "styles_custom_emotes_svg_chris-embarassed.svg", aliases: &[":oops:", ":blush:"] }, + Smilie { label: "Christine", file: "styles_custom_emotes_svg_chris-christine.svg", aliases: &[":christine:"] }, + Smilie { label: "Sad", file: "styles_custom_emotes_svg_chris-sad.svg", aliases: &[":(", ":sad:"] }, + Smilie { label: "Cringe", file: "styles_custom_emotes_svg_chris-cringe.svg", aliases: &[":cringe:"] }, + Smilie { label: "Mad", file: "styles_custom_emotes_svg_chris-mad.svg", aliases: &[":mad:", ">:(", ":@"] }, + Smilie { label: "Eek", file: "styles_custom_emotes_svg_chris-eek.svg", aliases: &[":o", ":eek:", ":shock:", ":shocked:"] }, + Smilie { label: "Evil", file: "styles_custom_emotes_svg_chris-evil.svg", aliases: &[":evil:", ">:)"] }, + Smilie { label: "Unholy", file: "styles_custom_emotes_svg_chris-unholy.svg", aliases: &[":unholy:", ":unholey:"] }, + Smilie { label: "Twisted", file: "styles_custom_emotes_svg_chris-twisted.svg", aliases: &[":twisted:"] }, + Smilie { label: "Punished", file: "styles_custom_emotes_svg_chris-punished.svg", aliases: &[":punished:"] }, + Smilie { label: "Neutral", file: "styles_custom_emotes_svg_chris-neutral.svg", aliases: &[":|", ":neutral:", ":meh:"] }, + Smilie { label: "Cool", file: "styles_custom_emotes_svg_chris-cool.svg", aliases: &[":cool:"] }, + Smilie { label: "Geek", file: "styles_custom_emotes_svg_chris-nerd.svg", aliases: &[":geek:", ":nerd:"] }, + Smilie { label: "Crying", file: "styles_custom_emotes_svg_chris-tears.svg", aliases: &[":'(", ":cry:"] }, + Smilie { label: "Crying Blood", file: "styles_custom_emotes_svg_chris-cryblood.svg", aliases: &[":cryblood:"] }, + Smilie { label: "Question", file: "styles_custom_emotes_svg_question.svg", aliases: &[":?:"] }, + Smilie { label: "Exclaim", file: "styles_custom_emotes_svg_exclamation.svg", aliases: &[":!:"] }, + Smilie { label: "Crashing into Slumber", file: "styles_custom_emotes_svg_chris-slumber.svg", aliases: &["(:_("] }, + Smilie { label: "Haters to the Left", file: "styles_default_xenforo_smilies_phpbb_icon_left.gif", aliases: &[":left:"] }, + Smilie { label: "Horse Face", file: "styles_default_xenforo_smilies_phpbb_icon_horse_face.gif", aliases: &[":c", ":C"] }, + Smilie { label: "Heart Empty", file: "styles_custom_emoji_Lovequest_200_202025.svg", aliases: &[":heart-empty:"] }, + Smilie { label: "Heart Full", file: "styles_custom_emoji_Lovequest_20100_202025.svg", aliases: &[":heart-full:"] }, + Smilie { label: "Ow.", file: "styles_custom_emotes_ow.gif", aliases: &[":ow:", ":rake:", ":amish:"] }, + Smilie { label: "Hi A-Log!", file: "styles_default_xenforo_smilies_phpbb_icon_alog.gif", aliases: &[":alog:", ":nice bloke:"] }, + Smilie { label: "Hi Anna!", file: "styles_default_xenforo_smilies_phpbb_icon_anna.gif", aliases: &[":anna:"] }, + Smilie { label: "Asperchu", file: "styles_default_xenforo_smilies_phpbb_icon_asperchu.png", aliases: &[":asperchu:"] }, + Smilie { label: "Sighduck", file: "styles_default_xenforo_smilies_phpbb_icon_sighduck.gif", aliases: &[":sighduck:"] }, + Smilie { label: "Snorlax", file: "styles_default_xenforo_smilies_phpbb_icon_snorlax.gif", aliases: &[":snorlax:"] }, + Smilie { label: "Squirtle", file: "styles_default_xenforo_smilies_phpbb_icon_squirtle.gif", aliases: &["*yawn*", "*YAWN*", ":squirtle:"] }, + Smilie { label: "Sonichu", file: "styles_default_xenforo_smilies_phpbb_icon_sonichu.png", aliases: &[":sonichu:"] }, + Smilie { label: "Dang, Dirty Troll", file: "styles_default_xenforo_smilies_phpbb_icon_troll.gif", aliases: &["}:P"] }, + Smilie { label: "Pickleman", file: "styles_default_xenforo_smilies_phpbb_icon_pickleman.gif", aliases: &[":pickleman:"] }, + Smilie { label: "Julay", file: "styles_default_xenforo_smilies_phpbb_icon_julay.png", aliases: &[":julay:"] }, + Smilie { label: "Ween", file: "styles_default_xenforo_smilies_phpbb_icon_ween.gif", aliases: &[":epik:", ":ween:", ":cool guy:", ":weensvg:"] }, + Smilie { label: "Stupid", file: "styles_default_xenforo_smilies_phpbb_icon_poll.png", aliases: &[":stupid:"] }, + Smilie { label: "Stressed", file: "styles_default_xenforo_smilies_phpbb_icon_stressed.png", aliases: &["|:("] }, + Smilie { label: "Sonichu Medallion", file: "styles_default_xenforo_smilies_phpbb_icon_medallion.gif", aliases: &[":medallion:"] }, + Smilie { label: "Q-Sand", file: "styles_default_xenforo_smilies_phpbb_icon_qsand.gif", aliases: &[":qsand:"] }, + Smilie { label: "Pickle", file: "styles_default_xenforo_smilies_phpbb_icon_pickle.gif", aliases: &[":pickle:"] }, + Smilie { label: "Fapcup", file: "styles_default_xenforo_smilies_phpbb_icon_fapcup.gif", aliases: &[":fapcup:"] }, + Smilie { label: "DIRTY, CRAPPED BRIEFS", file: "styles_default_xenforo_smilies_phpbb_icon_briefs.gif", aliases: &[":briefs:"] }, + Smilie { label: "Tomgirl", file: "styles_default_xenforo_smilies_phpbb_icon_tomgirl.gif", aliases: &[":tomgirl:"] }, + Smilie { label: "Tugboat", file: "styles_default_xenforo_smilies_phpbb_icon_tugboat.gif", aliases: &[":tugboat:"] }, + Smilie { label: "(Is this thing on? Oh!) Boo hoo hoo ~ !", file: "styles_custom_emotes_crocodile.png", aliases: &[":crocodile:"] }, + Smilie { label: "Analchest Souljer", file: "styles_default_xenforo_smilies_phpbb_icon_tyce.png", aliases: &[":tyceknife:"] }, + Smilie { label: "Eli", file: "styles_custom_emotes_jace_Of1Yvii.png", aliases: &[":eli:"] }, + Smilie { label: "Horses can't even get pussy...", file: "styles_custom_emotes_jace_Uw8NCT5.png", aliases: &[":eli3:"] }, + Smilie { label: "I've unchained the beast within.", file: "styles_custom_emotes_jace_7wA9pXj.png", aliases: &[":tycesuit:"] }, + Smilie { label: "im warier", file: "styles_custom_emotes_jace_ifPazCJ.png", aliases: &[":tyceskullmask:"] }, + Smilie { label: "My blade is a tool of justice.", file: "styles_custom_emotes_jace_uZd19Bc.png", aliases: &[":jaceknife:"] }, + Smilie { label: "Oh baby, a triple!", file: "styles_custom_emotes_jace_0XKIXBh.png", aliases: &[":jacewow:"] }, + Smilie { label: "Press 1", file: "styles_dark_ratings_press_1.png", aliases: &[":1:"] }, + Smilie { label: "Press 2", file: "styles_dark_ratings_press_2.png", aliases: &[":2:"] }, + Smilie { label: "RESPECT ME", file: "styles_custom_emotes_jace_ylo9W2m.png", aliases: &[":jacefedora:"] }, + Smilie { label: "Semper Fidelis to all are armed forces.", file: "styles_custom_emotes_jace_NYuMHTI.png", aliases: &[":semperfidelis:"] }, + Smilie { label: "She's not a dog, she's a WOLF", file: "styles_custom_emotes_jace_a6181SW.png", aliases: &[":soap:"] }, + Smilie { label: "They don't call me Head Shot Jace for nothing.", file: "styles_custom_emotes_jace_CHJMi6Q.png", aliases: &[":stickup:"] }, + Smilie { label: "Wow Dude Its Called Recycling", file: "styles_custom_emotes_jace_2dDorQI.png", aliases: &[":eli2:"] }, + Smilie { label: "WTF Face", file: "styles_custom_emotes_jace_cLeOgy3.png", aliases: &[":jacewtf:"] }, + Smilie { label: "Desert Eagle (Left)", file: "styles_default_xenforo_smilies_phpbb_icon_deagle_left.png", aliases: &[":deagleleft:"] }, + Smilie { label: "Desert Eagle (Right)", file: "styles_default_xenforo_smilies_phpbb_icon_deagle.png", aliases: &[":deagle:", ":deagleright:", ":deserteagle:"] }, + Smilie { label: "Steyr AUG (Left)", file: "styles_default_xenforo_smilies_phpbb_icon_aug_left.png", aliases: &[":augleft:"] }, + Smilie { label: "Steyr AUG (Right)", file: "styles_default_xenforo_smilies_phpbb_icon_aug_right.png", aliases: &[":aug:", ":augright:"] }, + Smilie { label: "Grilled Cheese", file: "styles_default_xenforo_smilies_phpbb_icon_grilledcheese.gif", aliases: &[":csand:", ":grilledcheese:", ":cheese:"] }, + Smilie { label: "Jace", file: "styles_default_xenforo_smilies_phpbb_icon_jace.png", aliases: &[":jace:", ":parkourdude91:", ":tacticool:"] }, + Smilie { label: "#TYCED", file: "styles_default_xenforo_smilies_phpbb_icon_tyced.png", aliases: &["#TYCED"] }, + Smilie { label: "Jace Pout", file: "styles_custom_emotes_jace_oLzPk8e.png", aliases: &[":jacepout:"] }, + Smilie { label: "Reply Button", file: "styles_custom_emotes_post-reply.png", aliases: &[":reply:", ":anom:", ":anominous:"] }, + Smilie { label: "Male", file: "styles_custom_emotes_male.png", aliases: &[":male:", ":gorilla:"] }, + Smilie { label: "Pinetar", file: "styles_custom_emotes_pinetar.png", aliases: &[":pinetar:", ":rape:"] }, + Smilie { label: "Mortal Wombat", file: "styles_custom_emotes_mortal-wombat.png", aliases: &[":wombat:", ":mortalwombat:"] }, + Smilie { label: "Billdozer", file: "styles_default_xenforo_smilies_phpbb_icon_billdozer.gif", aliases: &[":dunsparce:", ":bill:", ":billdozer:"] }, + Smilie { label: "Die, Null", file: "styles_custom_emotes_dienull.gif", aliases: &[":dienull:"] }, + Smilie { label: "Lewd! ... But I like it.", file: "styles_custom_emotes_panda.gif", aliases: &[":panda:", ":lewd:"] }, + Smilie { label: "Applecat", file: "styles_custom_emotes_applecat.webp", aliases: &[":applecat:"] }, + Smilie { label: "Coster", file: "styles_default_xenforo_smilies_phpbb_icon_coster.png", aliases: &[":coster:"] }, + Smilie { label: "Paul Blart", file: "styles_default_xenforo_smilies_phpbb_icon_paulblart.png", aliases: &[":blart:", ":paulblart:"] }, + Smilie { label: "brooklynbailiff", file: "styles_default_xenforo_smilies_phpbb_icon_brooklynbailiff.gif", aliases: &[":bb:", ":brooklynbailiff:"] }, + Smilie { label: "CatParty", file: "styles_default_xenforo_smilies_phpbb_icon_catparty.gif", aliases: &[":catparty:"] }, + Smilie { label: "champthom", file: "styles_default_xenforo_smilies_phpbb_icon_champthom.gif", aliases: &[":champ:", ":champthom:"] }, + Smilie { label: "Drunk Mythos", file: "styles_default_xenforo_smilies_phpbb_icon_boozemythos.png", aliases: &[":drunkmythos:", ":boozemythos:"] }, + Smilie { label: "drunkenhobo", file: "styles_default_xenforo_smilies_phpbb_icon_krystal.png", aliases: &[":hobo:", ":drunkenhobo:", ":dh:"] }, + Smilie { label: "Freecell", file: "styles_default_xenforo_smilies_phpbb_icon_freecell.gif", aliases: &[":freecell:"] }, + Smilie { label: "Hulk Hogan", file: "styles_default_xenforo_smilies_phpbb_icon_hulk.png", aliases: &[":hulk:", ":hulkhogan:", ":hulkster:"] }, + Smilie { label: "Hunger Mythos", file: "styles_default_xenforo_smilies_phpbb_icon_mythos.jpg", aliases: &[":mythos:", ":hungermythos:"] }, + Smilie { label: "KatsuKitty", file: "styles_default_xenforo_smilies_phpbb_icon_katsukitty.gif", aliases: &[":katsu:", ":katsukitty:"] }, + Smilie { label: "The Knife", file: "styles_default_xenforo_smilies_phpbb_icon_knife.gif", aliases: &[":knife:"] }, + Smilie { label: "Glaive", file: "styles_custom_emotes_glaive.png", aliases: &[":glaive:"] }, + Smilie { label: "Melchett", file: "styles_default_xenforo_smilies_phpbb_icon_melchett.gif", aliases: &[":melchett:", ":mel:"] }, + Smilie { label: "Null", file: "styles_custom_emotes_dog.gif", aliases: &[":null:"] }, + Smilie { label: "Dove", file: "styles_default_xenforo_smilies_phpbb_snakesvsplanes.png", aliases: &[":dove:", ":mourningdove:", ":snakes:"] }, + Smilie { label: "silentprincess", file: "styles_default_xenforo_smilies_phpbb_icon_silentprincess.png", aliases: &[":sp:", ":silentprincess:", ":silent:", ":fairy"] }, + Smilie { label: "Smokedaddy", file: "styles_default_xenforo_smilies_phpbb_icon_smokedaddy.gif", aliases: &[":smokedaddy:", ":smoke:"] }, + Smilie { label: "Sunshine Goodness", file: "styles_default_xenforo_smilies_phpbb_icon_sunshine_goodness.png", aliases: &[":sunshinegoodness:", ":sg:", ":luna:"] }, + Smilie { label: "Waifu", file: "styles_default_xenforo_smilies_phpbb_icon_waifu.png", aliases: &[":waifu:", ":faggotnerd:"] }, + Smilie { label: "Waifu Sad", file: "styles_custom_emotes_waifu-sad.png", aliases: &[":sadwaifu:"] }, + Smilie { label: "He is your host, the man they call Ghost.", file: "styles_custom_emotes_ghost.png", aliases: &[":ghost:"] }, + Smilie { label: "WOOOOOOOOOOOOO", file: "styles_custom_emotes_ghost-woo.gif", aliases: &[":woo:"] }, + Smilie { label: "HAH HAH HAAAAAAH", file: "styles_custom_emotes_ghost-laugh.gif", aliases: &[":hah:", ":ghost-laugh:"] }, + Smilie { label: "cans.wav", file: "styles_custom_emotes_cans.gif", aliases: &[":cans:"] }, + Smilie { label: "Johnny Walker, Blue Label, Oh yeaaaa!", file: "styles_custom_emotes_johnny-walker-blue-label-oh-yea.png", aliases: &[":bluelabel:", ":johnny:", ":walker:", ":johnnywalker:"] }, + Smilie { label: "Hambone", file: "styles_custom_emotes_hambone.png", aliases: &[":hambone:"] }, + Smilie { label: "Notice me, Ghost!", file: "styles_custom_emotes_notice-me.gif", aliases: &[":noticeme:"] }, + Smilie { label: "BEEJAMS!", file: "styles_custom_emotes_bj.png", aliases: &[":beejams:"] }, + Smilie { label: "Bogged", file: "styles_custom_emotes_bogged.png", aliases: &[":bogged:"] }, + Smilie { label: "Chicken", file: "styles_custom_emotes_chicken.gif", aliases: &[":chicken:"] }, + Smilie { label: "Cunning Pepe", file: "styles_custom_emotes_cunning-pepe.png", aliases: &[":cunningpepe:"] }, + Smilie { label: "fucking gold", file: "styles_custom_emotes_gold.png", aliases: &[":gold:"] }, + Smilie { label: "Smug", file: "styles_custom_emotes_something-smug.gif", aliases: &[":smug:"] }, + Smilie { label: "Thinking", file: "styles_custom_emotes_thinking-face_1f914.png", aliases: &[":thinking:"] }, + Smilie { label: "Grab", file: "styles_custom_emotes_Grab.png", aliases: &[":grab:"] }, + Smilie { label: "【guntwave】", file: "styles_custom_emotes_gunt.gif", aliases: &[":gunt:"] }, + Smilie { label: "Filthy Grin", file: "styles_custom_emotes_filthy.gif", aliases: &[":philthy:"] }, + Smilie { label: "Salt", file: "styles_custom_emotes_salt.gif", aliases: &[":salt:"] }, + Smilie { label: "Shit Eating Grin", file: "styles_custom_emotes_shit-eating.jpg", aliases: &[":shit-eating:", ":shiteating:"] }, + Smilie { label: "ARE YOU GUVKING HAPPY?", file: "styles_custom_emotes_spudking.png", aliases: &[":spudking:", ":guvking:"] }, + Smilie { label: "Bernie Cuck", file: "styles_custom_emotes_cuck.png", aliases: &[":cuck:", ":sanders:", ":bernie:", ":suck:"] }, + Smilie { label: "Reality", file: "styles_custom_emotes_reality.png", aliases: &[":reality:"] }, + Smilie { label: "Hillary Clinton", file: "attachments_cunt-png.63843.png", aliases: &[":hillary:", ":shillary:", ":cunt:"] }, + Smilie { label: "Stressing Out", file: "styles_custom_emotes_dsp-stress.png", aliases: &[":stress:", ":stressing:"] }, + Smilie { label: "Wall!", file: "styles_custom_emotes_icon_wall.gif", aliases: &[":wall:"] }, + Smilie { label: "Trump", file: "styles_custom_emotes_trump.png", aliases: &[":trump:"] }, + Smilie { label: "Eyes that scream", file: "styles_custom_emotes_suffering.png", aliases: &[":suffering:"] }, + Smilie { label: "Bork", file: "styles_default_xenforo_smilies_phpbb_icon_bork.png", aliases: &[":bork:"] }, + Smilie { label: "Slayer Gnome", file: "styles_default_xenforo_smilies_phpbb_icon_slayer.png", aliases: &[":slayer:", ":gayer:", ":gnome:"] }, + Smilie { label: "Special Snowflake", file: "styles_default_xenforo_smilies_phpbb_icon_snowflake.gif", aliases: &[":tumblr:", ":tumblrite:", ":snowflake:"] }, + Smilie { label: "Coffee (Left)", file: "styles_default_xenforo_smilies_phpbb_icon_coffee_left.png", aliases: &[":coffeeleft:"] }, + Smilie { label: "Coffee (Right)", file: "styles_default_xenforo_smilies_phpbb_icon_coffee.png", aliases: &[":coffee:", ":coffeeright:"] }, + Smilie { label: "AUGH YEAH", file: "styles_default_xenforo_smilies_phpbb_icon_augh_yeah.png", aliases: &["AUGH YEAH"] }, + Smilie { label: "Neckbeard", file: "styles_default_xenforo_smilies_phpbb_icon_neckbeard.gif", aliases: &[":neckbeard:"] }, + Smilie { label: "Popcorn", file: "styles_default_xenforo_smilies_phpbb_icon_popcorn.gif", aliases: &[":popcorn:"] }, + Smilie { label: "Ruse Cruise", file: "styles_default_xenforo_smilies_phpbb_icon_ruse.png", aliases: &[":ruse:"] }, + Smilie { label: "Sub", file: "styles_default_xenforo_smilies_phpbb_icon_sub.png", aliases: &[":sub:"] }, + Smilie { label: "That's... That's an eggplant!", file: "styles_custom_emotes_eggplant.png", aliases: &[":eggplant:", ":dick:"] }, + Smilie { label: "What a Story!", file: "styles_default_xenforo_smilies_phpbb_icon_story.gif", aliases: &[":story:"] }, + Smilie { label: "StrikerWolf", file: "styles_custom_emotes_striker.webp", aliases: &[":striker:", ":strikerwolf:", ":wolfguy:"] }, + Smilie { label: "wow", file: "styles_default_xenforo_smilies_phpbb_icon_doge_wow.gif", aliases: &[":wow:"] }, + Smilie { label: "gay", file: "styles_custom_emotes_gay.png", aliases: &[":gay:"] }, + Smilie { label: "nice dude!", file: "styles_default_xenforo_smilies_phpbb_icon_nice.gif", aliases: &[":nicedude:"] }, + Smilie { label: "Like", file: "styles_custom_emoji_Like.svg", aliases: &[":like:"] }, + Smilie { label: "Dislike", file: "styles_custom_emoji_Dislike.svg", aliases: &[":dislike:"] }, + Smilie { label: "Agree", file: "styles_custom_emoji_Agree.svg", aliases: &[":agree:"] }, + Smilie { label: "Disagree", file: "styles_custom_emoji_Disagree.svg", aliases: &[":disagree:"] }, + Smilie { label: "Achievement", file: "styles_custom_emoji_Achievement.svg", aliases: &[":achievement:"] }, + Smilie { label: "Null's Love", file: "styles_custom_emoji_Mutt_20Love.svg", aliases: &[":mutted:", ":muttlove:"] }, + Smilie { label: "Winner", file: "styles_custom_emoji_Winner.svg", aliases: &[":winner:"] }, + Smilie { label: "Semper Fidelis", file: "styles_custom_emoji_Semper_20Fidelis.svg", aliases: &[":semperfi:"] }, + Smilie { label: "Informative", file: "styles_custom_emoji_Informative.svg", aliases: &[":informative:"] }, + Smilie { label: "Feels", file: "styles_custom_emoji_Feels.svg", aliases: &[":feels:"] }, + Smilie { label: "Optimistic", file: "styles_custom_emoji_Optimistic.svg", aliases: &[":optimistic:"] }, + Smilie { label: "DRINK", file: "styles_custom_emoji_Drink.svg", aliases: &[":drink:"] }, + Smilie { label: "Lunacy", file: "styles_custom_emoji_Lunacy.svg", aliases: &[":lunacy:", ":lunatic:", "🌙"] }, + Smilie { label: "Political Sperging", file: "styles_custom_emoji_Moai.svg", aliases: &[":politisperg:", ":politicalsperging:", ":politicalsperg:", "🗿"] }, + Smilie { label: "Coom", file: "styles_custom_emotes_dspcoom.png", aliases: &[":coom:", ":dspCoom:"] }, + Smilie { label: "Diddler", file: "styles_custom_emotes_diddler2.png", aliases: &[":diddler:"] }, + Smilie { label: "Fat Pat", file: "styles_custom_emotes_fatpat.png", aliases: &[":tomlinson:", ":fatpat:"] }, + Smilie { label: "Felted", file: "attachments_felted-gif.6203542.gif", aliases: &[":felted:"] }, + Smilie { label: "Nite nite time", file: "attachments_nitenite-gif.6203539.gif", aliases: &[":nitenite:", ":nightnight:"] }, + Smilie { label: "Ramona (formerly Ralph)", file: "attachments_ramona-ralph-png.6203404.png", aliases: &[":ramona:", ":bluehair:"] }, + Smilie { label: "Ratface", file: "styles_custom_emotes_ChantalSmug.png", aliases: &[":ratface:"] }, + Smilie { label: "Really, nigga?", file: "styles_custom_emotes_rekieta_really.webp", aliases: &[":really:"] }, + Smilie { label: "Sorry, Gator!", file: "styles_custom_emotes_gator.webp", aliases: &[":gator:"] }, + Smilie { label: "I can't believe it, I lost it all man!", file: "styles_custom_emotes_bmj_loss.png", aliases: &[":lossmanjack:", ":bossmanjack:", ":bossman:"] }, + Smilie { label: "Juice!", file: "styles_custom_emotes_bmj_juicy.gif", aliases: &[":juice:"] }, + Smilie { label: "Ross, the Destroyer of Juice", file: "styles_custom_emotes_bmj_ross_hq.png", aliases: &[":ross:"] }, +]; diff --git a/chatd-rs/chatd/src/model.rs b/chatd-rs/chatd/src/model.rs index e149a7d..dea01b3 100644 --- a/chatd-rs/chatd/src/model.rs +++ b/chatd-rs/chatd/src/model.rs @@ -94,6 +94,13 @@ pub struct Reaction { pub emoji: String, pub count: i64, pub me: bool, + /// Only meaningful for a custom Discord emoji (see backend/discord.rs's + /// extract_reactions) - whether the frontend should build its CDN + /// image URL with a `.gif` extension instead of `.png`. Always false + /// for a plain Unicode emoji, which the frontend renders as text and + /// never looks at this for. + #[serde(default)] + pub animated: bool, } /// A Discord rich embed (title/description/color/timestamp box - a diff --git a/chatd-rs/chatd/src/rpc/methods.rs b/chatd-rs/chatd/src/rpc/methods.rs index ddc3d7c..f3dc7c4 100644 --- a/chatd-rs/chatd/src/rpc/methods.rs +++ b/chatd-rs/chatd/src/rpc/methods.rs @@ -24,6 +24,19 @@ pub async fn dispatch( Some(buffer_id) => (Some(serde_json::json!(state.runtime.get_discord_buffer_emojis(buffer_id))), None), }, + // Sneedchat's own site-wide smiley table (see backend/sockchat/ + // smilies.rs) - unlike Discord's per-guild emoji above, this is + // fixed and global, so no bufferId is needed. `file` is a filename + // within the plugin's own bundled assets/sockchat-smilies/ dir, + // not a URL - these images ship with the plugin rather than being + // fetched from kiwifarms.st at runtime, so the frontend resolves + // the actual file:// path itself (it already knows its own + // install directory) and this needs no I/O of any kind to answer. + "listSockchatSmilies" => ( + Some(serde_json::json!(backend::sockchat::smilies::SMILIES.iter().map(|s| serde_json::json!({ "label": s.label, "aliases": s.aliases, "file": s.file })).collect::>())), + None, + ), + "listProtocols" => ( Some(serde_json::json!([ { "id": "irc", "name": "IRC" }, @@ -317,6 +330,29 @@ pub async fn dispatch( } } + // Adds or removes *our own* reaction on a message - toggled from + // the client side (see MessageList.qml's reaction pills/hover + // toolbar), Discord-only for now since Sneedchat has no reaction + // concept of its own. + "toggleReaction" => { + let (buffer_id, msg_id, emoji) = match (p_str_opt(params, "bufferId"), p_str_opt(params, "messageId"), p_str_opt(params, "emoji")) { + (Some(b), Some(m), Some(e)) => (b, m, e), + _ => return (None, Some("toggleReaction requires \"bufferId\", \"messageId\" and \"emoji\"".to_string())), + }; + let add = p_bool(params, "add", true); + match state.runtime.get_buffer(buffer_id) { + None => (None, Some("no such buffer".to_string())), + Some(buffer) if buffer.account_id.starts_with("discord:") => match state.accounts.get_discord(&buffer.account_id) { + None => (None, Some("account not connected".to_string())), + Some(cfg) => match backend::discord::toggle_reaction(state, buffer_id, &cfg.token, msg_id, emoji, add).await { + Ok(()) => (Some(ok_node()), None), + Err(e) => (None, Some(e.to_string())), + }, + }, + Some(_) => (None, Some("reactions aren't supported for this service".to_string())), + } + } + "deleteMessage" => { let (buffer_id, msg_id) = match (p_str_opt(params, "bufferId"), p_str_opt(params, "messageId")) { (Some(b), Some(m)) => (b, m), diff --git a/chatd-rs/chatd/src/store.rs b/chatd-rs/chatd/src/store.rs index 4123bdb..077f965 100644 --- a/chatd-rs/chatd/src/store.rs +++ b/chatd-rs/chatd/src/store.rs @@ -158,7 +158,15 @@ impl Store { r.me = add; } } else if add { - reactions.push(Reaction { emoji: emoji.to_string(), count: 1, me: is_me }); + // animated defaults false here rather than being threaded + // through from the live gateway event - a brand-new reaction + // (never seen on this message before) is a rare first-add, + // and the next full backlog fetch/reconnect re-syncs it with + // the real value from the message's own snapshot (see + // extract_reactions in backend/discord.rs, which does know + // it) - not worth widening this incremental-update path just + // for that narrow a window. + reactions.push(Reaction { emoji: emoji.to_string(), count: 1, me: is_me, animated: false }); } reactions.retain(|r| r.count > 0); diff --git a/views/ChatView.qml b/views/ChatView.qml index b020ab8..e9bef3f 100644 --- a/views/ChatView.qml +++ b/views/ChatView.qml @@ -304,6 +304,8 @@ Item { root.buffers = bufs; root.markBufferRead(bufferId); root.fetchBufferEmoji(bufferId); + if (root.activeBufferIsSockchat) + root.fetchSockchatSmilies(); if (alreadyActive) return; send("subscribe", { "bufferId": bufferId }); @@ -1002,6 +1004,14 @@ print(path) const e = Object.assign({}, root.bufferEmojiByBuffer); e[bufferId] = msg.result; root.bufferEmojiByBuffer = e; + } else if (msg.id === "sockchatSmilies" && msg.result) { + // chatd only knows the bundled filename (see + // smilies.rs's own doc comment on why - the images + // ship with the plugin, chatd doesn't resolve paths + // outside its own data dir) - the actual file:// URL + // is built here instead, from this side's own + // knowledge of where the plugin is installed. + root.sockchatSmilies = msg.result.map(s => Object.assign({}, s, { "url": root.sockchatSmilieAssetDir ? "file://" + root.sockchatSmilieAssetDir + s.file : "" })); } } } @@ -1020,6 +1030,35 @@ print(path) send("listBufferEmoji", { "bufferId": bufferId }, "bufferEmoji:" + bufferId); } + // Sneedchat's own site-wide smiley table (see backend/sockchat/ + // smilies.rs) - unlike bufferEmojiByBuffer above this isn't per-buffer + // (the same ~175 shortcodes apply to every room on the site) and, since + // the images ship bundled with the plugin (assets/sockchat-smilies/) + // rather than being fetched from kiwifarms.st at runtime, the table + // never changes at runtime either - fetched once and memoized, same + // as fetchBufferEmoji's own per-key guard. + property var sockchatSmilies: [] + property bool sockchatSmiliesFetched: false + + // Where the plugin's own bundled assets live - same getPluginPath + // resolution ChatDaemon.qml/DaemonStatusSettings.qml already use for + // their own on-disk paths, just for this one asset folder rather than + // the chatd binary or a settings-panel git check. + readonly property string sockchatSmilieAssetDir: { + const p = PluginService.getPluginPath("dankChat"); + if (!p) + return ""; + const dir = p.endsWith("/") ? p : p + "/"; + return dir + "assets/sockchat-smilies/"; + } + + function fetchSockchatSmilies() { + if (root.sockchatSmiliesFetched) + return; + root.sockchatSmiliesFetched = true; + send("listSockchatSmilies", {}, "sockchatSmilies"); + } + RowLayout { anchors.fill: parent spacing: 0 @@ -1280,6 +1319,12 @@ print(path) messages: root.visibleMessages(root.activeBufferId) bufferId: root.activeBufferId isDiscord: root.activeBufferIsDiscord + isSockchat: root.activeBufferIsSockchat + sockchatSmilies: root.sockchatSmilies + // Only meaningful for a Discord buffer's own reaction + // picker (see MessageList.qml's reactionPicker) - same + // data the composer's own emoji button already uses. + customEmoji: root.bufferEmojiByBuffer[root.activeBufferId] || [] canEditDelete: root.activeBufferSupportsEditDelete dividerTs: root.dividerTsByBuffer[root.activeBufferId] || 0 comfyMode: root.messageDisplayMode === "comfy" @@ -1308,6 +1353,7 @@ print(path) onEditSubmitted: (id, newBody) => root.editMessage(id, newBody) onDeleteRequested: id => root.deleteMessage(id) onRetryRequested: id => root.retrySend(id) + onReactionToggled: (id, emoji, add) => root.send("toggleReaction", { "bufferId": root.activeBufferId, "messageId": id, "emoji": emoji, "add": add }) onLoadMoreRequested: root.loadMoreHistory(root.activeBufferId) } } @@ -1442,6 +1488,10 @@ print(path) // list, which is already filtered to // what's actually usable there. customEmoji: root.bufferEmojiByBuffer[root.activeBufferId] || [] + // Only offered for a Sneedchat buffer - see + // fetchSockchatSmilies, fetched once and + // memoized the first time one becomes active. + sockchatSmilies: root.activeBufferIsSockchat ? root.sockchatSmilies : [] onEmojiSelected: text => { inputField.text += text; inputField.forceActiveFocus(); diff --git a/views/EmojiPicker.qml b/views/EmojiPicker.qml index a1e7a1e..512fb59 100644 --- a/views/EmojiPicker.qml +++ b/views/EmojiPicker.qml @@ -5,18 +5,24 @@ import qs.Common import qs.Services import qs.Widgets -// Emoji insertion popup for the message input - two sections: a small -// curated set of common Unicode emoji (works on every protocol, since -// they're just text) and, when the active buffer is a Discord channel -// belonging to a guild, that guild's own custom emoji (see ChatView.qml's -// bufferEmojiByBuffer/backend/discord.rs's register_guild_channels, which -// already filters to only what's actually usable there - an emoji that -// lost its slot on a de-boosted guild never reaches this list at all, so -// there's nothing further to check here). +// Emoji insertion popup for the message input - a search box up top, then +// (when non-empty) a "Recent" row, then three sections: a small curated +// set of common Unicode emoji (works on every protocol, since they're +// just text), Discord's own per-guild custom emoji when the active buffer +// belongs to one (see ChatView.qml's bufferEmojiByBuffer/backend/ +// discord.rs's register_guild_channels, which already filters to only +// what's actually usable there), and Sneedchat's own bundled smiley table +// when the active buffer is a Sneedchat room. Popup { id: root property var customEmoji: [] // [{id, name, animated}] + // Sneedchat's own site-wide smiley table - bundled with the plugin + // (assets/sockchat-smilies/, see backend/sockchat/smilies.rs), not + // fetched from kiwifarms.st. [{label, aliases, url}] - url is only + // ever empty if ChatView.qml's own plugin-path resolution failed + // (falls back to a text-only row below rather than showing nothing). + property var sockchatSmilies: [] signal emojiSelected(string text) @@ -24,14 +30,129 @@ Popup { // handful of categories/reactions that come up constantly in chat, // the same practical scope line as this project's other "good enough, // not exhaustive" cuts (see MediaEmbed.qml's YouTube-thumbnail-only - // note). - readonly property var commonEmoji: ["😀", "😂", "😅", "😊", "😉", "😍", "🤔", "😎", "😢", "😭", "😡", "😱", "🥳", "🤯", "🙄", "😴", "👍", "👎", "👏", "🙌", "🙏", "💪", "🤝", "👋", "❤️", "🔥", "💯", "✨", "🎉", "🎂", "✅", "❌", "⚠️", "💀", "🤷", "🫡"] + // note). Named (not just the bare characters) so the search box below + // has something to actually match against - "fire" finding 🔥 only + // works if something here says so. + readonly property var commonEmojiList: [ + { "emoji": "😀", "name": "grinning happy" }, + { "emoji": "😂", "name": "joy laughing tears" }, + { "emoji": "😅", "name": "sweat smile nervous" }, + { "emoji": "😊", "name": "smiling blush" }, + { "emoji": "😉", "name": "wink" }, + { "emoji": "😍", "name": "heart eyes love" }, + { "emoji": "🤔", "name": "thinking" }, + { "emoji": "😎", "name": "cool sunglasses" }, + { "emoji": "😢", "name": "sad cry" }, + { "emoji": "😭", "name": "sobbing crying bawling" }, + { "emoji": "😡", "name": "angry mad rage" }, + { "emoji": "😱", "name": "scream shocked" }, + { "emoji": "🥳", "name": "party celebrate" }, + { "emoji": "🤯", "name": "mind blown" }, + { "emoji": "🙄", "name": "eye roll" }, + { "emoji": "😴", "name": "sleepy tired sleeping" }, + { "emoji": "👍", "name": "thumbs up like" }, + { "emoji": "👎", "name": "thumbs down dislike" }, + { "emoji": "👏", "name": "clap applause" }, + { "emoji": "🙌", "name": "raised hands celebrate" }, + { "emoji": "🙏", "name": "pray please thanks" }, + { "emoji": "💪", "name": "muscle strong flex" }, + { "emoji": "🤝", "name": "handshake deal" }, + { "emoji": "👋", "name": "wave hello bye" }, + { "emoji": "❤️", "name": "heart love red" }, + { "emoji": "🔥", "name": "fire lit hot" }, + { "emoji": "💯", "name": "hundred perfect" }, + { "emoji": "✨", "name": "sparkles shiny" }, + { "emoji": "🎉", "name": "party tada celebration" }, + { "emoji": "🎂", "name": "birthday cake" }, + { "emoji": "✅", "name": "check mark yes done" }, + { "emoji": "❌", "name": "x cross no wrong" }, + { "emoji": "⚠️", "name": "warning caution" }, + { "emoji": "💀", "name": "skull dead" }, + { "emoji": "🤷", "name": "shrug idk" }, + { "emoji": "🫡", "name": "salute respect" }, + ] + + // Normalized {text, imageUrl, display, search} - text is what gets + // inserted, display is the plain-text fallback when there's no image + // (or before one loads), search is what the fuzzy finder matches + // against. One shape shared by every section (including Recent) so + // selection/recording/rendering only needs writing once. + readonly property var discordItems: root.customEmoji.map(e => ({ + "text": "<:" + e.name + ":" + e.id + ">", + "imageUrl": "https://cdn.discordapp.com/emojis/" + e.id + (e.animated ? ".gif" : ".png"), + "display": e.name, + "search": e.name.toLowerCase() + })) + readonly property var sockchatItems: root.sockchatSmilies.map(s => { + const primary = (s.aliases && s.aliases.length > 0) ? s.aliases[0] : ""; + return { + "text": primary, + "imageUrl": s.url || "", + "display": primary, + "search": (s.label + " " + (s.aliases || []).join(" ")).toLowerCase() + }; + }) + readonly property var commonItems: root.commonEmojiList.map(c => ({ + "text": c.emoji, + "imageUrl": "", + "display": c.emoji, + "search": c.name.toLowerCase() + })) + + property string searchQuery: "" + + // Subsequence match (query characters appear in order, not + // necessarily contiguous) - the usual definition of "fuzzy" a finder + // like this is expected to use, e.g. "fre" still finds "fire". + function fuzzyMatch(query, text) { + if (!query) + return true; + let qi = 0; + for (let i = 0; i < text.length && qi < query.length; i++) { + if (text[i] === query[qi]) + qi++; + } + return qi === query.length; + } + + readonly property string normalizedQuery: root.searchQuery.trim().toLowerCase() + readonly property var filteredDiscord: root.normalizedQuery ? root.discordItems.filter(i => root.fuzzyMatch(root.normalizedQuery, i.search)) : root.discordItems + readonly property var filteredSockchat: root.normalizedQuery ? root.sockchatItems.filter(i => root.fuzzyMatch(root.normalizedQuery, i.search)) : root.sockchatItems + readonly property var filteredCommon: root.normalizedQuery ? root.commonItems.filter(i => root.fuzzyMatch(root.normalizedQuery, i.search)) : root.commonItems + readonly property bool hasNoResults: root.normalizedQuery !== "" && root.filteredDiscord.length === 0 && root.filteredSockchat.length === 0 && root.filteredCommon.length === 0 + + // Last 7 distinct emoji actually picked, most-recent first - persisted + // across sessions the same way every other small per-plugin list in + // this app is (see e.g. ChatView.qml's blockedNicks). Deliberately + // not kept live-in-sync across multiple open ChatView instances (a + // popout window's own picker vs the main panel's) - low-stakes if one + // is briefly stale, not worth the extra Connections/globalVar + // plumbing for a convenience list like this. + property var recentEmoji: PluginService.loadPluginData("dankChat", "recentEmoji", []) + + function recordRecent(item) { + const withoutDupe = root.recentEmoji.filter(r => r.text !== item.text); + const updated = [item].concat(withoutDupe).slice(0, 7); + root.recentEmoji = updated; + PluginService.savePluginData("dankChat", "recentEmoji", updated); + } + + function selectItem(item) { + root.recordRecent(item); + root.emojiSelected(item.text); + root.close(); + } width: 280 - height: 320 + height: 380 padding: Theme.spacingS closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + onOpened: { + root.searchQuery = ""; + searchField.forceActiveFocus(); + } + background: Rectangle { color: BlurService.enabled ? Theme.surfaceContainer : Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) radius: Theme.cornerRadius @@ -39,9 +160,88 @@ Popup { border.color: Theme.outlineStrong } + // One shared tile shape for every section (Recent included) - an + // image when the item has one (Discord CDN, or a bundled Sneedchat + // smiley), shown at its own native size rather than squeezed into a + // fixed box (same "full size" call already made for the chat buffer's + // own inline smilies - see MessageList.qml), otherwise its plain-text + // `display` (a Unicode emoji character, or a shortcode when a + // Sneedchat image failed to resolve) in a fixed 36x36 box, which has + // no "native size" of its own to size to. Defined before contentItem, + // matching this codebase's own convention for inline components (see + // e.g. DMS's own WindowRuleModal.qml) even though QML doesn't require + // the ordering. + component EmojiTile: Rectangle { + id: tile + required property var item + signal selected + + readonly property bool hasImage: !!tile.item.imageUrl + // Caps how wide a single tile can grow to - a handful of the + // bundled Sneedchat smilies are large enough (up to 64x64) that + // showing them at true native size could overflow past the + // picker's own edge here, unlike a message bubble which can just + // grow wider. Read from the actual Flow this tile ends up + // parented into (a Repeater's delegates are parented straight + // into whatever contains the Repeater) rather than a hardcoded + // guess at the popup's own width, so it stays correct even if + // that width ever changes. + readonly property real maxTileDim: tile.parent ? Math.max(24, tile.parent.width - Theme.spacingS * 2) : 200 + readonly property real imageScale: tileImage.implicitWidth > 0 ? Math.min(1, tile.maxTileDim / tileImage.implicitWidth) : 1 + + width: tile.hasImage ? tileImage.implicitWidth * tile.imageScale + Theme.spacingXS * 2 : 36 + height: tile.hasImage ? tileImage.implicitHeight * tile.imageScale + Theme.spacingXS * 2 : 36 + radius: Theme.cornerRadius / 2 + color: tileHover.hovered ? Theme.surfaceContainerHigh : "transparent" + + HoverHandler { + id: tileHover + } + + Image { + id: tileImage + anchors.centerIn: parent + width: implicitWidth * tile.imageScale + height: implicitHeight * tile.imageScale + visible: tile.hasImage + asynchronous: true + fillMode: Image.PreserveAspectFit + source: tile.item.imageUrl || "" + } + + StyledText { + anchors.fill: parent + anchors.margins: 2 + visible: !tile.hasImage + text: tile.item.display || "" + font.pixelSize: tile.item.display && tile.item.display.length <= 2 ? Theme.fontSizeLarge : 9 + color: Theme.surfaceVariantText + wrapMode: Text.WrapAnywhere + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: tile.selected() + } + } + contentItem: ColumnLayout { spacing: Theme.spacingS + DankTextField { + id: searchField + Layout.fillWidth: true + height: 34 + placeholderText: "Search emoji..." + leftIconName: "search" + showClearButton: true + text: root.searchQuery + onTextChanged: root.searchQuery = text + } + Flickable { Layout.fillWidth: true Layout.fillHeight: true @@ -59,7 +259,48 @@ Popup { StyledText { Layout.fillWidth: true - visible: root.customEmoji.length > 0 + visible: root.hasNoResults + text: "No emoji found" + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + horizontalAlignment: Text.AlignHCenter + } + + StyledText { + Layout.fillWidth: true + visible: !root.normalizedQuery && root.recentEmoji.length > 0 + text: "Recent" + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Medium + color: Theme.surfaceVariantText + } + + Flow { + Layout.fillWidth: true + visible: !root.normalizedQuery && root.recentEmoji.length > 0 + spacing: Theme.spacingXXS + + Repeater { + model: root.recentEmoji + + EmojiTile { + required property var modelData + item: modelData + onSelected: root.selectItem(modelData) + } + } + } + + Rectangle { + Layout.fillWidth: true + visible: !root.normalizedQuery && root.recentEmoji.length > 0 + height: 1 + color: Theme.outlineMedium + } + + StyledText { + Layout.fillWidth: true + visible: root.filteredDiscord.length > 0 text: "This server" font.pixelSize: Theme.fontSizeSmall font.weight: Font.Medium @@ -68,53 +309,62 @@ Popup { Flow { Layout.fillWidth: true - visible: root.customEmoji.length > 0 + visible: root.filteredDiscord.length > 0 spacing: Theme.spacingXXS Repeater { - model: root.customEmoji + model: root.filteredDiscord - Rectangle { - id: customDelegate + EmojiTile { required property var modelData - width: 36 - height: 36 - radius: Theme.cornerRadius / 2 - color: customHover.hovered ? Theme.surfaceContainerHigh : "transparent" - - HoverHandler { - id: customHover - } - - Image { - anchors.fill: parent - anchors.margins: 4 - asynchronous: true - fillMode: Image.PreserveAspectFit - source: "https://cdn.discordapp.com/emojis/" + customDelegate.modelData.id + (customDelegate.modelData.animated ? ".gif" : ".png") - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: { - root.emojiSelected("<:" + customDelegate.modelData.name + ":" + customDelegate.modelData.id + ">"); - root.close(); - } - } + item: modelData + onSelected: root.selectItem(modelData) } } } Rectangle { Layout.fillWidth: true - visible: root.customEmoji.length > 0 + visible: root.filteredDiscord.length > 0 height: 1 color: Theme.outlineMedium } StyledText { Layout.fillWidth: true + visible: root.filteredSockchat.length > 0 + text: "Smilies" + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Medium + color: Theme.surfaceVariantText + } + + Flow { + Layout.fillWidth: true + visible: root.filteredSockchat.length > 0 + spacing: Theme.spacingXXS + + Repeater { + model: root.filteredSockchat + + EmojiTile { + required property var modelData + item: modelData + onSelected: root.selectItem(modelData) + } + } + } + + Rectangle { + Layout.fillWidth: true + visible: root.filteredSockchat.length > 0 + height: 1 + color: Theme.outlineMedium + } + + StyledText { + Layout.fillWidth: true + visible: root.filteredCommon.length > 0 text: "Common" font.pixelSize: Theme.fontSizeSmall font.weight: Font.Medium @@ -123,37 +373,16 @@ Popup { Flow { Layout.fillWidth: true + visible: root.filteredCommon.length > 0 spacing: Theme.spacingXXS Repeater { - model: root.commonEmoji + model: root.filteredCommon - Rectangle { - id: commonDelegate - required property string modelData - width: 36 - height: 36 - radius: Theme.cornerRadius / 2 - color: commonHover.hovered ? Theme.surfaceContainerHigh : "transparent" - - HoverHandler { - id: commonHover - } - - StyledText { - anchors.centerIn: parent - text: commonDelegate.modelData - font.pixelSize: Theme.fontSizeLarge - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: { - root.emojiSelected(commonDelegate.modelData); - root.close(); - } - } + EmojiTile { + required property var modelData + item: modelData + onSelected: root.selectItem(modelData) } } } diff --git a/views/MessageList.qml b/views/MessageList.qml index 5b49c1d..e78f6ee 100644 --- a/views/MessageList.qml +++ b/views/MessageList.qml @@ -30,6 +30,19 @@ ListView { // is gated separately by canEditDelete below, since Sneedchat supports // those too despite having no native-reply concept of its own. property bool isDiscord: false + // Gates smiley-shortcode rendering (see substituteSmilies) to only + // Sneedchat buffers - a bare ":)" or ";)" in an IRC/Discord message is + // just text there and must never turn into an image. + property bool isSockchat: false + // Sneedchat's site-wide smiley table, bundled with the plugin rather + // than fetched from kiwifarms.st - see ChatView.qml's sockchatSmilies/ + // fetchSockchatSmilies. [{label, aliases, url}]. + property var sockchatSmilies: [] + // The active buffer's guild's own custom Discord emoji - same data + // ChatView.qml's own composer EmojiPicker already uses + // (bufferEmojiByBuffer), threaded through here too for the shared + // reaction picker below (reactionPicker/openReactionPicker). + property var customEmoji: [] // Whether the active buffer's service supports editing/deleting your // own messages at all (Discord and Sneedchat today) - see ChatView.qml's // activeBufferSupportsEditDelete. @@ -174,8 +187,66 @@ ListView { signal editSubmitted(string id, string newBody) signal deleteRequested(string id) signal retryRequested(string id) + // add true to add our own reaction, false to remove it - see the + // reaction pills/hover toolbar below. Discord-only (see isDiscord). + signal reactionToggled(string id, string emoji, bool add) signal loadMoreRequested() + // A handful of one-click quick reactions on the hover toolbar (see + // msgRow's hoverToolbar below) - Discord itself shows "frequently + // used" emoji there, which would need its own usage-tracking; these + // are just a fixed, commonly-reached-for set instead, the same + // practical scope cut as EmojiPicker.qml's own commonEmoji list. + readonly property var quickReactEmoji: ["👍", "❤️", "😂", "🔥"] + + // Single shared EmojiPicker for "add a reaction" (from either the + // hover toolbar's own button or the "+" pill after existing + // reactions) rather than one Popup instance per message row - with + // up to maxMessagesPerBuffer rows, that would mean hundreds of idle + // Popup instances for one feature. reactingToId tracks which + // message's reactions the next emoji pick actually applies to. + property string reactingToId: "" + + function openReactionPicker(id, anchorItem) { + root.reactingToId = id; + reactionPicker.parent = anchorItem; + // Right-aligned to the anchor by default (its own right edge, + // not its left, lines up with the anchor's right edge) rather + // than the previous left-aligned x:0 - the hover toolbar's own + // "add reaction" button sits right at the row's right edge (see + // hoverToolbar), which in a narrow panel is also close to the + // window's own right edge, so opening left-aligned there pushed + // the picker's full 280px width straight past it. Then clamped + // against all four edges of the actual visible message list + // (root, this ListView) using the anchor's real position within + // it, so it stays fully on screen regardless of which button + // opened it, how wide the panel currently is, or how close to + // the bottom of the visible list the reacted-to message is. + const anchorPos = anchorItem.mapToItem(root, 0, 0); + let x = anchorItem.width - reactionPicker.width; + if (anchorPos.x + x < 0) + x = -anchorPos.x; + if (anchorPos.x + x + reactionPicker.width > root.width) + x = root.width - reactionPicker.width - anchorPos.x; + reactionPicker.x = x; + + let y = anchorItem.height + Theme.spacingXXS; + if (anchorPos.y + y + reactionPicker.height > root.height) + y = -reactionPicker.height - Theme.spacingXXS; + reactionPicker.y = y; + + reactionPicker.open(); + } + + EmojiPicker { + id: reactionPicker + customEmoji: root.customEmoji + onEmojiSelected: text => { + if (root.reactingToId) + root.reactionToggled(root.reactingToId, text, true); + } + } + // Scrolls to a message already present in the currently-loaded page - // the reply-preview click target. If the original has scrolled out of // the locally loaded window (paginated away, or never fetched this @@ -879,6 +950,66 @@ ListView { return out; } + // Alias -> {label, aliases, url} - rebuilt only when sockchatSmilies + // itself changes (once per fetch, see ChatView.qml), not per message. + readonly property var sockchatSmilieIndex: { + const map = {}; + for (const s of root.sockchatSmilies) { + for (const a of (s.aliases || [])) + map[a] = s; + } + return map; + } + + // One compiled alternation of every known shortcode, longest first so + // e.g. a shorter alias can never pre-empt a longer one that happens to + // contain it. Built once per sockchatSmilies fetch, reused for every + // visible message row rather than rebuilt per-row. + readonly property var sockchatSmilieRegex: { + const aliases = Object.keys(root.sockchatSmilieIndex); + if (aliases.length === 0) + return null; + const sorted = aliases.slice().sort((a, b) => b.length - a.length); + const escaped = sorted.map(a => a.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")); + return new RegExp(escaped.join("|"), "g"); + } + + // gif/webp are the only formats this app ever actually animates (see + // MediaEmbed.qml's own isAnimatedImage - same two extensions, same + // reasoning: AnimatedImage is what plays them, plain Image only ever + // shows a static first frame). + function isAnimatedSmilieUrl(url) { + return /\.(gif|webp)$/i.test(url); + } + + // Finds every animated-smiley match in `text` (deduped by url) so + // they can be rendered as real AnimatedImage items instead of the + // inline tags formatMessage below uses for everything else - + // Qt's StyledText/RichText image support has no concept of an + // animated at all, only ever painting its static first frame, + // so a gif/webp smiley needs an actual QML Image-family item to + // loop. A second independent pass over the same text formatMessage + // itself reads, matching this file's own existing precedent + // (extractMedia/extractCodeBlocks/extractQuoteBlocks are all already + // separate passes over msgRow.normalizedBody rather than one + // do-everything function). + function extractAnimatedSmilies(text) { + if (!root.isSockchat || !root.sockchatSmilieRegex || !text) + return []; + const re = new RegExp(root.sockchatSmilieRegex.source, "g"); + const seen = new Set(); + const out = []; + let match; + while ((match = re.exec(text)) !== null) { + const s = root.sockchatSmilieIndex[match[0]]; + if (s && s.url && root.isAnimatedSmilieUrl(s.url) && !seen.has(s.url)) { + seen.add(s.url); + out.push(s); + } + } + return out; + } + // `revealedSpoilers` is a per-message {index: true} map (see msgRow's // own revealedSpoilers property) - spoilers are numbered in the order // they appear in this call, so the same message always maps the same @@ -929,6 +1060,43 @@ ListView { return stow(`${root.escapeHtml(inner)}`); }); + // Sneedchat smiley shortcodes (":)", ":heart-empty:", "*sigh*", + // etc. - see sockchatSmilieRegex/sockchatSmilieIndex above) - only + // for a Sneedchat buffer, and only after code/anchor/spoiler + // content is already stowed away above so this never reaches + // inside a code span or a not-yet-revealed spoiler. Must also run + // before the markdown bold/italic pass further down, since + // several real shortcodes ("*sigh*", "*YAWN*") are themselves + // shaped like markdown emphasis and would otherwise be mangled + // into sigh before ever being recognized as a smiley. + if (root.isSockchat && root.sockchatSmilieRegex) { + out = out.replace(root.sockchatSmilieRegex, m => { + const s = root.sockchatSmilieIndex[m]; + // s.url is only empty if ChatView.qml's own plugin-path + // resolution failed (see its sockchatSmilies doc comment) + // - leave the plain shortcode text visible rather than a + // broken image reference in that case. + if (!s || !s.url) + return m; + // Animated (gif/webp) smilies are left as plain shortcode + // text here and rendered separately, as a real + // AnimatedImage, by msgRow's own animatedSmilies row below + // - see extractAnimatedSmilies's doc comment for why an + // inline tag can't do this (Qt's rich text image + // support only ever paints a static first frame). + if (root.isAnimatedSmilieUrl(s.url)) + return m; + // No explicit width/height - Qt's rich text renders an + // with no size attributes at the image's own native + // pixel size, which is what "full size" means here: most + // of these are already small by design (24-64px), and + // forcing every one to a fixed square previously distorted + // the handful of wider banner-shaped ones (e.g. gunt.gif + // at 68x18) instead of just showing them as-is. + return stow(``); + }); + } + // Markdown links: [label](url) out = out.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, (m, label, href) => stow(`${label}`)); @@ -1145,7 +1313,29 @@ ListView { width: ListView.view.width height: dividerRow.height + contentRow.implicitHeight + Theme.spacingXS * 2 - color: modelData.isHighlight ? Theme.withAlpha(Theme.primary, 0.12) : "transparent" + color: { + if (modelData.isHighlight) + return Theme.withAlpha(Theme.primary, 0.12); + // Every protocol, not just Discord - a plain "you're looking + // at this row" highlight has nothing protocol-specific about + // it. hoverToolbar just below stays Discord-only though, since + // reactions/native-reply are the only things there and IRC/ + // Sneedchat have neither (see hoverToolbar's own doc comment). + if (rowHover.hovered) + return Theme.surfaceContainerHigh; + return "transparent"; + } + + // Drives the row hover highlight above (every protocol) and the + // hover toolbar/reaction affordances below (Discord-only, since + // reactions and native reply are the only things there - see + // hoverToolbar's own doc comment). Covers the whole row (not just + // the body text) so hovering anywhere - the avatar, empty space + // past a short message - shows it, the same as Discord's own + // row-wide hover area. + HoverHandler { + id: rowHover + } // BBCode-normalized once here rather than in each of the // properties below individually - see normalizeBBCode's own @@ -1157,6 +1347,11 @@ ListView { readonly property var codeBlocks: root.extractCodeBlocks(msgRow.normalizedBody) readonly property var quoteBlocks: root.extractQuoteBlocks(root.stripCodeBlocks(msgRow.normalizedBody)) readonly property string displayBody: root.stripQuoteBlocks(root.stripCodeBlocks(root.stripEmbeddedUrls(msgRow.normalizedBody, msgRow.mediaItems))) + // Sneedchat smiley shortcodes in this message that resolve to an + // animated (gif/webp) image - see extractAnimatedSmilies's own + // doc comment for why these can't just be inline tags like + // the rest. Rendered as their own row below the body text instead. + readonly property var animatedSmilies: root.extractAnimatedSmilies(msgRow.displayBody) // Which ||spoiler|| runs (by position in this message) the user has // already clicked to reveal - keyed by index, not by content, so // two identical spoilers in one message can be revealed @@ -1414,6 +1609,31 @@ ListView { } } + // Animated (gif/webp) Sneedchat smilies used in this message - + // see msgRow.animatedSmilies/extractAnimatedSmilies. Native + // size, same as the inline static ones in bodyText above, and + // always looping - there's no autoplay/loop setting to respect + // here the way MediaEmbed.qml's own animated images have + // (mediaAutoplay/mediaLoop), since a smiley the user + // deliberately typed into a message is the whole point of + // typing it, not incidental page content to maybe suppress. + Flow { + Layout.fillWidth: true + visible: msgRow.animatedSmilies.length > 0 + spacing: Theme.spacingXS + + Repeater { + model: msgRow.animatedSmilies + + AnimatedImage { + required property var modelData + source: modelData.url + playing: true + asynchronous: true + } + } + } + // Inline edit - opens in place of the normal body row above // (toggled via the visible bindings on each) rather than a // separate popup, so it reads as "this exact message became @@ -1605,11 +1825,14 @@ ListView { } } - // Reaction pills - custom Discord emoji (see backend/ - // discord.rs's "<:name:id>" convention) render as their name - // for now rather than the actual image; full custom-emoji - // image rendering is part of the emoji-picker milestone, not - // this one. + // Reaction pills - sized to match Discord's own (~24px tall), + // a real CDN image for a custom emoji (animated .gif vs + // static .png per its own `animated` flag - see model.rs's + // Reaction/backend/discord.rs's extract_reactions) rather + // than showing its name as text, plain Unicode rendered + // natively otherwise. Clickable - toggles *our* reaction on + // that emoji (add if we haven't, remove if we have), the same + // click-to-toggle behavior Discord's own pills have. Flow { Layout.fillWidth: true spacing: Theme.spacingXS @@ -1621,8 +1844,11 @@ ListView { Rectangle { id: reactionDelegate required property var modelData + readonly property var customEmojiMatch: reactionDelegate.modelData.emoji.match(/^$/) + readonly property string emojiImageUrl: reactionDelegate.customEmojiMatch ? "https://cdn.discordapp.com/emojis/" + reactionDelegate.customEmojiMatch[2] + (reactionDelegate.modelData.animated ? ".gif" : ".png") : "" + implicitWidth: reactionRow.implicitWidth + Theme.spacingS * 2 - implicitHeight: reactionRow.implicitHeight + Theme.spacingXXS * 2 + implicitHeight: 24 radius: Theme.cornerRadius / 2 color: modelData.me ? Theme.withAlpha(Theme.primary, 0.18) : Theme.surfaceContainerHigh border.width: modelData.me ? 1 : 0 @@ -1633,26 +1859,230 @@ ListView { anchors.centerIn: parent spacing: Theme.spacingXXS + Image { + Layout.preferredWidth: 16 + Layout.preferredHeight: 16 + visible: reactionDelegate.emojiImageUrl !== "" + asynchronous: true + fillMode: Image.PreserveAspectFit + source: reactionDelegate.emojiImageUrl + } + StyledText { - text: { - const m = reactionDelegate.modelData.emoji.match(/^<:([^:]+):(\d+)>$/); - return m ? ":" + m[1] + ":" : reactionDelegate.modelData.emoji; - } - font.pixelSize: Theme.fontSizeSmall + visible: reactionDelegate.emojiImageUrl === "" + text: reactionDelegate.modelData.emoji + font.pixelSize: Theme.fontSizeMedium } StyledText { text: reactionDelegate.modelData.count font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText + color: reactionDelegate.modelData.me ? Theme.primary : Theme.surfaceVariantText + font.weight: reactionDelegate.modelData.me ? Font.Medium : Font.Normal } } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.reactionToggled(msgRow.modelData.id, reactionDelegate.modelData.emoji, !reactionDelegate.modelData.me) + } + } + } + + // "+" pill after existing reactions - Discord's own + // shortcut to add a *different* emoji once some are + // already showing, alongside the hover toolbar's own + // add-reaction button above (which works even before any + // reaction exists yet). + Rectangle { + id: addReactionPill + width: 24 + height: 24 + radius: Theme.cornerRadius / 2 + color: addReactionPillHover.hovered ? Theme.surfaceContainerHighest : Theme.surfaceContainerHigh + + HoverHandler { + id: addReactionPillHover + } + + DankIcon { + anchors.centerIn: parent + name: "add_reaction" + size: 14 + color: Theme.surfaceVariantText + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.openReactionPicker(msgRow.modelData.id, addReactionPill) } } } } } + // Discord's own floating hover toolbar - quick one-click + // reactions, "add a reaction" (opens the full picker), Reply, and + // "..." (opens the same right-click menu below, so every action + // is still reachable without needing to actually right-click). + // z above everything else in the row since it deliberately + // overlaps the top-right corner rather than reserving its own + // layout space (same visual trick Discord's own client uses - + // it only exists on hover, so there's no reason to permanently + // budget height for it). + Rectangle { + id: hoverToolbar + visible: root.isDiscord && (rowHover.hovered || toolbarHover.hovered) + z: 10 + anchors.right: parent.right + anchors.top: parent.top + anchors.rightMargin: Theme.spacingM + anchors.topMargin: -Theme.spacingS + width: toolbarRow.implicitWidth + Theme.spacingS + height: toolbarRow.implicitHeight + Theme.spacingXXS * 2 + radius: Theme.cornerRadius + color: Theme.surfaceContainerHigh + border.width: 1 + border.color: Theme.outlineMedium + + HoverHandler { + id: toolbarHover + } + + RowLayout { + id: toolbarRow + anchors.centerIn: parent + spacing: Theme.spacingXXS + + Repeater { + model: root.quickReactEmoji + + Rectangle { + id: quickReactDelegate + required property string modelData + width: 28 + height: 28 + radius: Theme.cornerRadius / 2 + color: quickReactHover.hovered ? Theme.surfaceContainerHighest : "transparent" + + HoverHandler { + id: quickReactHover + } + + StyledText { + anchors.centerIn: parent + text: quickReactDelegate.modelData + font.pixelSize: Theme.fontSizeMedium + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.reactionToggled(msgRow.modelData.id, quickReactDelegate.modelData, true) + } + } + } + + // Separates the quick-react emoji from the action icons + // below, matching Discord's own hover toolbar - a small + // margin on both sides rather than relying on toolbarRow's + // own (much smaller) inter-item spacing alone, so it reads + // as a real section break instead of just another gap. + Rectangle { + Layout.leftMargin: Theme.spacingXXS + Layout.rightMargin: Theme.spacingXXS + Layout.preferredWidth: 1 + Layout.preferredHeight: 18 + color: Theme.outlineMedium + } + + // Same 28x28 hover-highlighted box shape as the quick-react + // tiles above (rather than a bare DankIcon with its own + // negative-margin MouseArea) - both for visual consistency + // and because bare icons sitting directly adjacent with no + // box of their own to separate them, at only 16-18px + // across, is what made them look crowded/overlapping in + // the first place. + Rectangle { + id: addReactionButton + width: 28 + height: 28 + radius: Theme.cornerRadius / 2 + color: addReactionHover.hovered ? Theme.surfaceContainerHighest : "transparent" + + HoverHandler { + id: addReactionHover + } + + DankIcon { + anchors.centerIn: parent + name: "add_reaction" + size: Theme.iconSizeSmall + color: Theme.surfaceVariantText + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.openReactionPicker(msgRow.modelData.id, addReactionButton) + } + } + + Rectangle { + id: replyButton + width: 28 + height: 28 + radius: Theme.cornerRadius / 2 + visible: !msgRow.modelData.isAction && !!msgRow.modelData.from + color: replyHover.hovered ? Theme.surfaceContainerHighest : "transparent" + + HoverHandler { + id: replyHover + } + + DankIcon { + anchors.centerIn: parent + name: "reply" + size: Theme.iconSizeSmall + color: Theme.surfaceVariantText + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.replyRequested(msgRow.modelData.id, msgRow.modelData.from, msgRow.modelData.body) + } + } + + Rectangle { + id: moreButton + width: 28 + height: 28 + radius: Theme.cornerRadius / 2 + color: moreHover.hovered ? Theme.surfaceContainerHighest : "transparent" + + HoverHandler { + id: moreHover + } + + DankIcon { + anchors.centerIn: parent + name: "more_vert" + size: Theme.iconSizeSmall + color: Theme.surfaceVariantText + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: contextMenu.popup() + } + } + } + } + // TapHandler rather than a covering MouseArea: an Item-based // MouseArea still claims hover priority over whatever it's // stacked on top of even when it only *accepts* the right button @@ -1680,16 +2110,34 @@ ListView { } MenuItem { + id: replyMenuItem text: "Reply" visible: !msgRow.modelData.isAction && !!msgRow.modelData.from height: visible ? implicitHeight : 0 + leftPadding: 0 - contentItem: StyledText { - text: parent.text - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceText - leftPadding: Theme.spacingS - verticalAlignment: Text.AlignVCenter + contentItem: Item { + implicitHeight: Math.max(replyIcon.height, replyLabel.implicitHeight) + + DankIcon { + id: replyIcon + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + name: "reply" + size: 16 + color: Theme.surfaceText + } + StyledText { + id: replyLabel + anchors.left: replyIcon.right + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + text: replyMenuItem.text + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + verticalAlignment: Text.AlignVCenter + } } background: Rectangle { @@ -1701,16 +2149,34 @@ ListView { } MenuItem { + id: editMenuItem text: "Edit" visible: root.canEditDelete && !!msgRow.modelData.isOwn height: visible ? implicitHeight : 0 + leftPadding: 0 - contentItem: StyledText { - text: parent.text - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceText - leftPadding: Theme.spacingS - verticalAlignment: Text.AlignVCenter + contentItem: Item { + implicitHeight: Math.max(editIcon.height, editLabel.implicitHeight) + + DankIcon { + id: editIcon + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + name: "edit" + size: 16 + color: Theme.surfaceText + } + StyledText { + id: editLabel + anchors.left: editIcon.right + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + text: editMenuItem.text + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + verticalAlignment: Text.AlignVCenter + } } background: Rectangle { @@ -1722,16 +2188,34 @@ ListView { } MenuItem { + id: deleteMenuItem text: "Delete" visible: root.canEditDelete && !!msgRow.modelData.isOwn height: visible ? implicitHeight : 0 + leftPadding: 0 - contentItem: StyledText { - text: parent.text - font.pixelSize: Theme.fontSizeSmall - color: Theme.error - leftPadding: Theme.spacingS - verticalAlignment: Text.AlignVCenter + contentItem: Item { + implicitHeight: Math.max(deleteIcon.height, deleteLabel.implicitHeight) + + DankIcon { + id: deleteIcon + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + name: "delete" + size: 16 + color: Theme.error + } + StyledText { + id: deleteLabel + anchors.left: deleteIcon.right + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + text: deleteMenuItem.text + font.pixelSize: Theme.fontSizeSmall + color: Theme.error + verticalAlignment: Text.AlignVCenter + } } background: Rectangle { @@ -1743,16 +2227,34 @@ ListView { } MenuItem { + id: retryMenuItem text: "Retry" visible: !!msgRow.modelData.isOwn && msgRow.modelData.sendStatus === "failed" height: visible ? implicitHeight : 0 + leftPadding: 0 - contentItem: StyledText { - text: parent.text - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceText - leftPadding: Theme.spacingS - verticalAlignment: Text.AlignVCenter + contentItem: Item { + implicitHeight: Math.max(retryIcon.height, retryLabel.implicitHeight) + + DankIcon { + id: retryIcon + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + name: "refresh" + size: 16 + color: Theme.surfaceText + } + StyledText { + id: retryLabel + anchors.left: retryIcon.right + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + text: retryMenuItem.text + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + verticalAlignment: Text.AlignVCenter + } } background: Rectangle { @@ -1764,14 +2266,32 @@ ListView { } MenuItem { + id: copyMenuItem text: "Copy text" + leftPadding: 0 - contentItem: StyledText { - text: parent.text - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceText - leftPadding: Theme.spacingS - verticalAlignment: Text.AlignVCenter + contentItem: Item { + implicitHeight: Math.max(copyIcon.height, copyLabel.implicitHeight) + + DankIcon { + id: copyIcon + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + name: "content_copy" + size: 16 + color: Theme.surfaceText + } + StyledText { + id: copyLabel + anchors.left: copyIcon.right + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + text: copyMenuItem.text + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + verticalAlignment: Text.AlignVCenter + } } background: Rectangle { @@ -1785,16 +2305,34 @@ ListView { } MenuItem { + id: openBrowserMenuItem text: "Open in browser" visible: msgRow.rowFirstUrl !== "" height: visible ? implicitHeight : 0 + leftPadding: 0 - contentItem: StyledText { - text: parent.text - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceText - leftPadding: Theme.spacingS - verticalAlignment: Text.AlignVCenter + contentItem: Item { + implicitHeight: Math.max(openBrowserIcon.height, openBrowserLabel.implicitHeight) + + DankIcon { + id: openBrowserIcon + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + name: "open_in_new" + size: 16 + color: Theme.surfaceText + } + StyledText { + id: openBrowserLabel + anchors.left: openBrowserIcon.right + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + text: openBrowserMenuItem.text + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + verticalAlignment: Text.AlignVCenter + } } background: Rectangle {