Fixed MM-33249 issue (#17475)
* Added logic to detect and set unicode emoji in the custom status slash command * Replaced strings.split with strings.Fields * Added logic to handle empty string as message in custom status slash command * Changed custom status slash command empty message behavior to set def… (#14) * Changed custom status slash command empty message behavior to set default emoji * Code refactoring * Added unit tests and refactored some code * WIP: Unit tests and refactoring for detecting unicode emoji in custom status slash commands * Complete unit testing for Get custom status * Fixed lint * Added logic for removing skin tone from unicode emoji (#16) * Added logic for removing skin tone from unicode emoji Made a reverse system emojis map of string vs []string and stored the emojiNames in sorted order Added the logic for detecting and replacing/removing skin tone in unicode emoji with variation selector Added new unit tests with different skin tone emojis * Refactored removeSkinTone logic to a separate function * Added check for emoji before removing skin tone in custom status slash command * Fixed custom status slash command unit test and refactored some code Chanded the return type of GetEmojiNameFromUnicode from bool to int Changed the logic for checking presence of emoji without removing skin tone Fixed the unit tests * Review fixes: Indentation changes
Этот коммит содержится в:
@@ -4,7 +4,9 @@
|
||||
package slashcommands
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/app"
|
||||
"github.com/mattermost/mattermost-server/v5/app/request"
|
||||
@@ -49,7 +51,7 @@ func (*CustomStatusProvider) DoCommand(a *app.App, c *request.Context, args *mod
|
||||
message = strings.TrimSpace(message)
|
||||
if message == CmdCustomStatusClear {
|
||||
if err := a.RemoveCustomStatus(args.UserId); err != nil {
|
||||
mlog.Error(err.Error())
|
||||
mlog.Debug(err.Error())
|
||||
return &model.CommandResponse{Text: args.T("api.command_custom_status.clear.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
|
||||
}
|
||||
|
||||
@@ -59,20 +61,9 @@ func (*CustomStatusProvider) DoCommand(a *app.App, c *request.Context, args *mod
|
||||
}
|
||||
}
|
||||
|
||||
customStatus := &model.CustomStatus{
|
||||
Emoji: DefaultCustomStatusEmoji,
|
||||
Text: message,
|
||||
}
|
||||
firstEmojiLocations := model.ALL_EMOJI_PATTERN.FindIndex([]byte(message))
|
||||
if len(firstEmojiLocations) > 0 && firstEmojiLocations[0] == 0 {
|
||||
// emoji found at starting index
|
||||
customStatus.Emoji = message[firstEmojiLocations[0]+1 : firstEmojiLocations[1]-1]
|
||||
customStatus.Text = strings.TrimSpace(message[firstEmojiLocations[1]:])
|
||||
}
|
||||
|
||||
customStatus.TrimMessage()
|
||||
customStatus := GetCustomStatus(message)
|
||||
if err := a.SetCustomStatus(args.UserId, customStatus); err != nil {
|
||||
mlog.Error(err.Error())
|
||||
mlog.Debug(err.Error())
|
||||
return &model.CommandResponse{Text: args.T("api.command_custom_status.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
|
||||
}
|
||||
|
||||
@@ -84,3 +75,69 @@ func (*CustomStatusProvider) DoCommand(a *app.App, c *request.Context, args *mod
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func GetCustomStatus(message string) *model.CustomStatus {
|
||||
customStatus := &model.CustomStatus{
|
||||
Emoji: DefaultCustomStatusEmoji,
|
||||
Text: message,
|
||||
}
|
||||
|
||||
firstEmojiLocations := model.ALL_EMOJI_PATTERN.FindIndex([]byte(message))
|
||||
if len(firstEmojiLocations) > 0 && firstEmojiLocations[0] == 0 {
|
||||
// emoji found at starting index
|
||||
customStatus.Emoji = message[firstEmojiLocations[0]+1 : firstEmojiLocations[1]-1]
|
||||
customStatus.Text = strings.TrimSpace(message[firstEmojiLocations[1]:])
|
||||
customStatus.TrimMessage()
|
||||
return customStatus
|
||||
}
|
||||
|
||||
if message == "" {
|
||||
return customStatus
|
||||
}
|
||||
|
||||
spaceSeparatedMessage := strings.Fields(message)
|
||||
if len(spaceSeparatedMessage) == 0 {
|
||||
return customStatus
|
||||
}
|
||||
|
||||
emojiString := spaceSeparatedMessage[0]
|
||||
var unicode []string
|
||||
for utf8.RuneCountInString(emojiString) >= 1 {
|
||||
codepoint, size := utf8.DecodeRuneInString(emojiString)
|
||||
code := model.RuneToHexadecimalString(codepoint)
|
||||
unicode = append(unicode, code)
|
||||
emojiString = emojiString[size:]
|
||||
}
|
||||
|
||||
unicodeString := removeUnicodeSkinTone(strings.Join(unicode, "-"))
|
||||
emoji, count := model.GetEmojiNameFromUnicode(unicodeString)
|
||||
if count > 0 {
|
||||
customStatus.Emoji = emoji
|
||||
textString := strings.Join(spaceSeparatedMessage[1:], " ")
|
||||
customStatus.Text = strings.TrimSpace(textString)
|
||||
}
|
||||
|
||||
customStatus.TrimMessage()
|
||||
return customStatus
|
||||
}
|
||||
|
||||
func removeUnicodeSkinTone(unicodeString string) string {
|
||||
skinToneDetectorRegex := regexp.MustCompile("-(1f3fb|1f3fc|1f3fd|1f3fe|1f3ff)")
|
||||
skinToneLocations := skinToneDetectorRegex.FindIndex([]byte(unicodeString))
|
||||
|
||||
if len(skinToneLocations) == 0 {
|
||||
return unicodeString
|
||||
}
|
||||
if _, count := model.GetEmojiNameFromUnicode(unicodeString); count == 1 {
|
||||
return unicodeString
|
||||
}
|
||||
unicodeWithRemovedSkinTone := unicodeString[:skinToneLocations[0]] + unicodeString[skinToneLocations[1]:]
|
||||
unicodeWithVariationSelector := unicodeString[:skinToneLocations[0]] + "-fe0f" + unicodeString[skinToneLocations[1]:]
|
||||
if _, count := model.GetEmojiNameFromUnicode(unicodeWithRemovedSkinTone); count > 0 {
|
||||
unicodeString = unicodeWithRemovedSkinTone
|
||||
} else if _, count := model.GetEmojiNameFromUnicode(unicodeWithVariationSelector); count > 0 {
|
||||
unicodeString = unicodeWithVariationSelector
|
||||
}
|
||||
|
||||
return unicodeString
|
||||
}
|
||||
|
||||
34
app/slashcommands/command_custom_status_test.go
Обычный файл
34
app/slashcommands/command_custom_status_test.go
Обычный файл
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package slashcommands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
)
|
||||
|
||||
func TestGetCustomStatus(t *testing.T) {
|
||||
for msg, expected := range map[string]model.CustomStatus{
|
||||
"": {Emoji: DefaultCustomStatusEmoji, Text: ""},
|
||||
"Hey": {Emoji: DefaultCustomStatusEmoji, Text: "Hey"},
|
||||
":cactus: Hurt": {Emoji: "cactus", Text: "Hurt"},
|
||||
"👅": {Emoji: "tongue", Text: ""},
|
||||
"👅 Eating": {Emoji: "tongue", Text: "Eating"},
|
||||
"💪🏻 Working out": {Emoji: "muscle_light_skin_tone", Text: "Working out"},
|
||||
"👙 Swimming": {Emoji: "bikini", Text: "Swimming"},
|
||||
"👙Swimming": {Emoji: DefaultCustomStatusEmoji, Text: "👙Swimming"},
|
||||
"👍🏿 Okay": {Emoji: "+1_dark_skin_tone", Text: "Okay"},
|
||||
"🤴🏾 Dark king": {Emoji: "prince_medium_dark_skin_tone", Text: "Dark king"},
|
||||
"⛹🏾♀️ Playing basketball": {Emoji: "basketball_woman", Text: "Playing basketball"},
|
||||
"🏋🏿♀️ Weightlifting": {Emoji: "weight_lifting_woman", Text: "Weightlifting"},
|
||||
"🏄 Surfing": {Emoji: "surfer", Text: "Surfing"},
|
||||
"👨👨👦👦 Family": {Emoji: "family_man_man_boy_boy", Text: "Family"},
|
||||
} {
|
||||
actual := GetCustomStatus(msg)
|
||||
if actual.Emoji != expected.Emoji || actual.Text != expected.Text {
|
||||
t.Errorf("expected `%v`, got `%v`", expected, *actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -39,6 +40,10 @@ func CustomStatusFromJson(data io.Reader) *CustomStatus {
|
||||
return cs
|
||||
}
|
||||
|
||||
func RuneToHexadecimalString(r rune) string {
|
||||
return fmt.Sprintf("%04x", r)
|
||||
}
|
||||
|
||||
type RecentCustomStatuses []CustomStatus
|
||||
|
||||
func (rcs *RecentCustomStatuses) Contains(cs *CustomStatus) bool {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,6 +24,8 @@ var EMOJI_PATTERN = regexp.MustCompile(`:[a-zA-Z0-9_-]+:`)
|
||||
// TODO: Merge ALL_EMOJI_PATTERN with EMOJI_PATTERN after updating custom emoji help texts
|
||||
var ALL_EMOJI_PATTERN = regexp.MustCompile(`:[a-zA-Z0-9_+-]+:`)
|
||||
|
||||
var ReverseSystemEmojisMap = makeReverseEmojiMap()
|
||||
|
||||
type Emoji struct {
|
||||
Id string `json:"id"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
@@ -42,6 +45,26 @@ func GetSystemEmojiId(emojiName string) (string, bool) {
|
||||
return id, found
|
||||
}
|
||||
|
||||
func makeReverseEmojiMap() map[string][]string {
|
||||
reverseEmojiMap := make(map[string][]string)
|
||||
for key, value := range SystemEmojis {
|
||||
emojiNames := reverseEmojiMap[value]
|
||||
emojiNames = append(emojiNames, key)
|
||||
sort.Strings(emojiNames)
|
||||
reverseEmojiMap[value] = emojiNames
|
||||
}
|
||||
|
||||
return reverseEmojiMap
|
||||
}
|
||||
|
||||
func GetEmojiNameFromUnicode(unicode string) (emojiName string, count int) {
|
||||
if emojiNames, found := ReverseSystemEmojisMap[unicode]; found {
|
||||
return emojiNames[0], len(emojiNames)
|
||||
}
|
||||
|
||||
return "", 0
|
||||
}
|
||||
|
||||
func (emoji *Emoji) IsValid() *AppError {
|
||||
if !IsValidId(emoji.Id) {
|
||||
return NewAppError("Emoji.IsValid", "model.emoji.id.app_error", nil, "", http.StatusBadRequest)
|
||||
|
||||
Ссылка в новой задаче
Block a user