diff --git a/bot/error.go b/bot/error.go index e529c89..6662c1d 100644 --- a/bot/error.go +++ b/bot/error.go @@ -16,6 +16,10 @@ type ErrUnknownCommand struct { } func (err *ErrUnknownCommand) Error() string { + return UnknownCommandString(err) +} + +var UnknownCommandString = func(err *ErrUnknownCommand) string { var header = "Unknown command: " + err.Prefix if err.Parent != "" { header += err.Parent + " " + err.Command @@ -35,10 +39,14 @@ type ErrInvalidUsage struct { // TODO: usage generator? // Here, as a reminder - ctx *CommandContext + Ctx *CommandContext } func (err *ErrInvalidUsage) Error() string { + return InvalidUsageString(err) +} + +var InvalidUsageString = func(err *ErrInvalidUsage) string { if err.Index == 0 { return "Invalid usage, error: " + err.Err } diff --git a/bot/extras/arguments/emoji.go b/bot/extras/arguments/emoji.go index e6fd4cd..7780d35 100644 --- a/bot/extras/arguments/emoji.go +++ b/bot/extras/arguments/emoji.go @@ -3,6 +3,7 @@ package arguments import ( "errors" "regexp" + "unicode/utf16" ) var ( @@ -54,17 +55,9 @@ func (e Emoji) URL() string { } func (e *Emoji) Parse(arg string) error { - // Check if Unicode - var unicode string - - for _, r := range arg { - if r < '\U0001F600' && r > '\U0001F64F' { - unicode += string(r) - } - } - - if unicode != "" { - e.ID = unicode + // Check if Unicode emoji + if stringIsEmojiOnly(arg) { + e.ID = arg e.Custom = false return nil @@ -83,3 +76,35 @@ func (e *Emoji) Parse(arg string) error { return nil } + +func stringIsEmojiOnly(emoji string) bool { + runes := []rune(emoji) + // Slice of runes is 2, since some emojis have 2 runes. + if len(runes) > 2 { + return false + } + + return emojiRune(runes[0]) +} + +var surrogates = [...][2]rune{ // [0] from, [1] to + {utf16.DecodeRune(0xD83C, 0xD000), utf16.DecodeRune(0xD83C, 0xDFFF)}, + {utf16.DecodeRune(0xD83E, 0xD000), utf16.DecodeRune(0xD83E, 0xDFFF)}, + {utf16.DecodeRune(0xD83F, 0xD000), utf16.DecodeRune(0xD83F, 0xDFFF)}, +} + +func emojiRune(r rune) bool { + b := r == '\u00a9' || r == '\u00ae' || + (r >= '\u2000' && r <= '\u3300') + if b { + return true + } + + for _, surrogate := range surrogates { + if surrogate[0] <= r && r <= surrogate[1] { + return true + } + } + + return false +} diff --git a/bot/extras/arguments/emoji_test.go b/bot/extras/arguments/emoji_test.go new file mode 100644 index 0000000..7fef1c2 --- /dev/null +++ b/bot/extras/arguments/emoji_test.go @@ -0,0 +1,28 @@ +package arguments + +import "testing" + +func TestEmojiRune(t *testing.T) { + var emojis = []string{ + "πŸ‘", + "❄️", + "🀲🏿", + } + + var notEmojis = []string{ + "πŸƒπŸΏπŸƒπŸΏ", // dual emojis + "te", // not emoji + } + + for i, emoji := range emojis { + if !stringIsEmojiOnly(emoji) { + t.Fatal(i, "is an emoji, function returned false") + } + } + + for i, not := range notEmojis { + if stringIsEmojiOnly(not) { + t.Fatal(i, "is not an emoji, function returned true") + } + } +}