diff --git a/lib/src/event.dart b/lib/src/event.dart index d369534..7807f88 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -762,4 +762,49 @@ class Event extends MatrixEvent { } return this; } + + /// returns if a message is a rich message + bool get isRichMessage => + content['format'] == 'org.matrix.custom.html' && + content['formatted_body'] is String; + + // regexes to fetch the number of emotes, including emoji, and if the message consists of only those + // to match an emoji we can use the following regex: + // \x{00a9}|\x{00ae}|[\x{2000}-\x{3300}]|\x{d83c}[\x{d000}-\x{dfff}|\x{d83d}[\x{d000}-\x{dfff}]|\x{d83e}[\x{d000}-\x{dfff}] + // we need to replace \x{0000} with \u0000, the comment is left in the other format to be able to paste into regex101.com + // to see if there is a custom emote, we use the following regex: ]+data-mx-(?:emote|emoticon)(?==|>|\s)[^>]*> + // now we combind the two to have four regexes: + // 1. are there only emoji, or whitespace + // 2. are there only emoji, emotes, or whitespace + // 3. count number of emoji + // 4- count number of emoji or emotes + static final RegExp _onlyEmojiRegex = RegExp( + r'^(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]|\s)*$', + caseSensitive: false, + multiLine: true); + static final RegExp _onlyEmojiEmoteRegex = RegExp( + r'^(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]|]+data-mx-(?:emote|emoticon)(?==|>|\s)[^>]*>|\s)*$', + caseSensitive: false, + multiLine: true); + static final RegExp _countEmojiRegex = RegExp( + r'(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])', + caseSensitive: false, + multiLine: true); + static final RegExp _countEmojiEmoteRegex = RegExp( + r'(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]|]+data-mx-(?:emote|emoticon)(?==|>|\s)[^>]*>)', + caseSensitive: false, + multiLine: true); + + /// Returns if a given event only has emotes, emojis or whitespace as content. + /// This is useful to determine if stand-alone emotes should be displayed bigger. + bool get onlyEmotes => isRichMessage + ? _onlyEmojiEmoteRegex.hasMatch(content['formatted_body']) + : _onlyEmojiRegex.hasMatch(content['body'] ?? ''); + + /// Gets the number of emotes in a given message. This is useful to determine if + /// emotes should be displayed bigger. WARNING: This does **not** test if there are + /// only emotes. Use `event.onlyEmotes` for that! + int get numberEmotes => isRichMessage + ? _countEmojiEmoteRegex.allMatches(content['formatted_body']).length + : _countEmojiRegex.allMatches(content['body'] ?? '').length; } diff --git a/test/event_test.dart b/test/event_test.dart index 9649b7c..3dabdd4 100644 --- a/test/event_test.dart +++ b/test/event_test.dart @@ -1159,5 +1159,103 @@ void main() { await room.client.dispose(closeDatabase: true); }); + test('emote detection', () async { + var event = Event.fromJson({ + 'type': EventTypes.Message, + 'content': { + 'msgtype': 'm.text', + 'body': 'normal message', + }, + 'event_id': '\$edit2', + 'sender': '@alice:example.org', + }, null); + expect(event.onlyEmotes, false); + expect(event.numberEmotes, 0); + event = Event.fromJson({ + 'type': EventTypes.Message, + 'content': { + 'msgtype': 'm.text', + 'body': 'normal message with emoji 🦊', + }, + 'event_id': '\$edit2', + 'sender': '@alice:example.org', + }, null); + expect(event.onlyEmotes, false); + expect(event.numberEmotes, 1); + event = Event.fromJson({ + 'type': EventTypes.Message, + 'content': { + 'msgtype': 'm.text', + 'body': '🦊', + }, + 'event_id': '\$edit2', + 'sender': '@alice:example.org', + }, null); + expect(event.onlyEmotes, true); + expect(event.numberEmotes, 1); + event = Event.fromJson({ + 'type': EventTypes.Message, + 'content': { + 'msgtype': 'm.text', + 'body': '🦊🦊 🦊\n🦊🦊', + }, + 'event_id': '\$edit2', + 'sender': '@alice:example.org', + }, null); + expect(event.onlyEmotes, true); + expect(event.numberEmotes, 5); + event = Event.fromJson({ + 'type': EventTypes.Message, + 'content': { + 'msgtype': 'm.text', + 'body': 'rich message', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'rich message' + }, + 'event_id': '\$edit2', + 'sender': '@alice:example.org', + }, null); + expect(event.onlyEmotes, false); + expect(event.numberEmotes, 0); + event = Event.fromJson({ + 'type': EventTypes.Message, + 'content': { + 'msgtype': 'm.text', + 'body': '🦊', + 'format': 'org.matrix.custom.html', + 'formatted_body': '🦊' + }, + 'event_id': '\$edit2', + 'sender': '@alice:example.org', + }, null); + expect(event.onlyEmotes, true); + expect(event.numberEmotes, 1); + event = Event.fromJson({ + 'type': EventTypes.Message, + 'content': { + 'msgtype': 'm.text', + 'body': ':blah:', + 'format': 'org.matrix.custom.html', + 'formatted_body': '' + }, + 'event_id': '\$edit2', + 'sender': '@alice:example.org', + }, null); + expect(event.onlyEmotes, true); + expect(event.numberEmotes, 1); + event = Event.fromJson({ + 'type': EventTypes.Message, + 'content': { + 'msgtype': 'm.text', + 'body': '🦊 :blah:', + 'format': 'org.matrix.custom.html', + 'formatted_body': '🦊 ' + }, + 'event_id': '\$edit2', + 'sender': '@alice:example.org', + }, null); + expect(event.onlyEmotes, true); + expect(event.numberEmotes, 2); + }); }); }