Only allow emojis as reactions.
This commit is contained in:
parent
d0986383ad
commit
ac0216d916
4 changed files with 101 additions and 0 deletions
|
@ -16,6 +16,7 @@ import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public final class EmojiUtil {
|
public final class EmojiUtil {
|
||||||
|
|
||||||
|
@ -42,6 +43,8 @@ public final class EmojiUtil {
|
||||||
MAX_EMOJI_LENGTH = max;
|
MAX_EMOJI_LENGTH = max;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Pattern EMOJI_PATTERN = Pattern.compile("^(?:(?:[\u00a9\u00ae\u203c\u2049\u2122\u2139\u2194-\u2199\u21a9-\u21aa\u231a-\u231b\u2328\u23cf\u23e9-\u23f3\u23f8-\u23fa\u24c2\u25aa-\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614-\u2615\u2618\u261d\u2620\u2622-\u2623\u2626\u262a\u262e-\u262f\u2638-\u263a\u2648-\u2653\u2660\u2663\u2665-\u2666\u2668\u267b\u267f\u2692-\u2694\u2696-\u2697\u2699\u269b-\u269c\u26a0-\u26a1\u26aa-\u26ab\u26b0-\u26b1\u26bd-\u26be\u26c4-\u26c5\u26c8\u26ce-\u26cf\u26d1\u26d3-\u26d4\u26e9-\u26ea\u26f0-\u26f5\u26f7-\u26fa\u26fd\u2702\u2705\u2708-\u270d\u270f\u2712\u2714\u2716\u271d\u2721\u2728\u2733-\u2734\u2744\u2747\u274c\u274e\u2753-\u2755\u2757\u2763-\u2764\u2795-\u2797\u27a1\u27b0\u27bf\u2934-\u2935\u2b05-\u2b07\u2b1b-\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299\ud83c\udc04\ud83c\udccf\ud83c\udd70-\ud83c\udd71\ud83c\udd7e-\ud83c\udd7f\ud83c\udd8e\ud83c\udd91-\ud83c\udd9a\ud83c\ude01-\ud83c\ude02\ud83c\ude1a\ud83c\ude2f\ud83c\ude32-\ud83c\ude3a\ud83c\ude50-\ud83c\ude51\u200d\ud83c\udf00-\ud83d\uddff\ud83d\ude00-\ud83d\ude4f\ud83d\ude80-\ud83d\udeff\ud83e\udd00-\ud83e\uddff\udb40\udc20-\udb40\udc7f]|\u200d[\u2640\u2642]|[\ud83c\udde6-\ud83c\uddff]{2}|.[\u20e0\u20e3\ufe0f]+)+)+$");
|
||||||
|
|
||||||
private EmojiUtil() {}
|
private EmojiUtil() {}
|
||||||
|
|
||||||
public static List<EmojiPageModel> getDisplayPages() {
|
public static List<EmojiPageModel> getDisplayPages() {
|
||||||
|
@ -90,4 +93,25 @@ public final class EmojiUtil {
|
||||||
return EmojiProvider.getInstance(context).getEmojiDrawable(emoji);
|
return EmojiProvider.getInstance(context).getEmojiDrawable(emoji);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the text is likely a single, valid emoji. Otherwise false.
|
||||||
|
*
|
||||||
|
* We do a two-tier check: first using our own knowledge of emojis (which could be incomplete),
|
||||||
|
* followed by a more wide check for all of the valid emoji unicode ranges (which could lead to
|
||||||
|
* some false positives). YMMV.
|
||||||
|
*/
|
||||||
|
public static boolean isEmoji(@NonNull Context context, @Nullable String text) {
|
||||||
|
if (Util.isEmpty(text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtil.getGraphemeCount(text) != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
EmojiParser.CandidateList candidates = EmojiProvider.getInstance(context).getCandidates(text);
|
||||||
|
|
||||||
|
return (candidates != null && candidates.size() > 0) || EMOJI_PATTERN.matcher(text).matches();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||||
import org.thoughtcrime.securesms.attachments.PointerAttachment;
|
import org.thoughtcrime.securesms.attachments.PointerAttachment;
|
||||||
import org.thoughtcrime.securesms.attachments.TombstoneAttachment;
|
import org.thoughtcrime.securesms.attachments.TombstoneAttachment;
|
||||||
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.Emoji;
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
|
||||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||||
import org.thoughtcrime.securesms.contactshare.ContactModelMapper;
|
import org.thoughtcrime.securesms.contactshare.ContactModelMapper;
|
||||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||||
|
@ -690,6 +692,11 @@ public final class MessageContentProcessor {
|
||||||
private void handleReaction(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) {
|
private void handleReaction(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) {
|
||||||
SignalServiceDataMessage.Reaction reaction = message.getReaction().get();
|
SignalServiceDataMessage.Reaction reaction = message.getReaction().get();
|
||||||
|
|
||||||
|
if (!EmojiUtil.isEmoji(context, reaction.getEmoji())) {
|
||||||
|
Log.w(TAG, "Reaction text is not a valid emoji! Ignoring the message.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Recipient targetAuthor = Recipient.externalPush(context, reaction.getTargetAuthor());
|
Recipient targetAuthor = Recipient.externalPush(context, reaction.getTargetAuthor());
|
||||||
MessageRecord targetMessage = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(reaction.getTargetSentTimestamp(), targetAuthor.getId());
|
MessageRecord targetMessage = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(reaction.getTargetSentTimestamp(), targetAuthor.getId());
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.text.BidiFormatter;
|
import androidx.core.text.BidiFormatter;
|
||||||
|
|
||||||
|
import org.signal.core.util.BreakIteratorCompat;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -265,4 +267,13 @@ public final class StringUtil {
|
||||||
int end = (maxChars - 1) - start;
|
int end = (maxChars - 1) - start;
|
||||||
return text.subSequence(0, start) + "…" + text.subSequence(text.length() - end, text.length());
|
return text.subSequence(0, start) + "…" + text.subSequence(text.length() - end, text.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The number of graphemes in the provided string.
|
||||||
|
*/
|
||||||
|
public static int getGraphemeCount(@NonNull CharSequence text) {
|
||||||
|
BreakIteratorCompat iterator = BreakIteratorCompat.getInstance();
|
||||||
|
iterator.setText(text);
|
||||||
|
return iterator.countBreaks();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
package org.thoughtcrime.securesms.components.emoji;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.ParameterizedRobolectricTestRunner;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||||
|
@Config(manifest = Config.NONE, application = Application.class)
|
||||||
|
public class EmojiUtilTest_isEmoji {
|
||||||
|
|
||||||
|
private final String input;
|
||||||
|
private final boolean output;
|
||||||
|
|
||||||
|
@ParameterizedRobolectricTestRunner.Parameters
|
||||||
|
public static Collection<Object[]> data() {
|
||||||
|
return Arrays.asList(new Object[][]{
|
||||||
|
{ null, false },
|
||||||
|
{ "", false },
|
||||||
|
{ "cat", false },
|
||||||
|
{ "ᑢᗩᖶ", false },
|
||||||
|
{ "♍︎♋︎⧫︎", false },
|
||||||
|
{ "ᑢ", false },
|
||||||
|
{ "¯\\_(ツ)_/¯", false},
|
||||||
|
{ "\uD83D\uDE0D", true }, // Smiling face with heart-shaped eyes
|
||||||
|
{ "\uD83D\uDD77", true }, // Spider
|
||||||
|
{ "\uD83E\uDD37", true }, // Person shrugging
|
||||||
|
{ "\uD83E\uDD37\uD83C\uDFFF\u200D♂️", true }, // Man shrugging dark skin tone
|
||||||
|
{ "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66", true }, // Family: Man, Woman, Girl, Boy
|
||||||
|
{ "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDC67\uD83C\uDFFB\u200D\uD83D\uDC66\uD83C\uDFFB", true }, // Family - Man: Light Skin Tone, Woman: Light Skin Tone, Girl: Light Skin Tone, Boy: Light Skin Tone (NOTE: Not widely supported, good stretch test)
|
||||||
|
{ "\uD83D\uDE0Dhi", false }, // Smiling face with heart-shaped eyes, text afterwards
|
||||||
|
{ "\uD83D\uDE0D ", false }, // Smiling face with heart-shaped eyes, space afterwards
|
||||||
|
{ "\uD83D\uDE0D\uD83D\uDE0D", false }, // Smiling face with heart-shaped eyes, twice
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public EmojiUtilTest_isEmoji(String input, boolean output) {
|
||||||
|
this.input = input;
|
||||||
|
this.output = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isEmoji() {
|
||||||
|
Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
|
assertEquals(output, EmojiUtil.isEmoji(context, input));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue