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 @@
+
+
+
+
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 {