owobot/internal/emoji/emoji.go

179 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* owobot - Your server's guardian and entertainer
* Copyright (C) 2023 owobot Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package emoji
import (
"regexp"
"strings"
"unicode"
"github.com/rivo/uniseg"
)
var (
customEmojiRegex = regexp.MustCompile(`^<(a?):(\w+):(\d+)>$`)
unicodeEmojiTable = &unicode.RangeTable{
R32: []unicode.Range32{
{ // Enclosed Alphanumeric Supplement
Lo: 0x1F100,
Hi: 0x1F1FF,
Stride: 1,
},
{ // Miscellaneous Symbols and Pictographs
Lo: 0x1F300,
Hi: 0x1F5FF,
Stride: 1,
},
{ // Emoticons
Lo: 0x1F600,
Hi: 0x1F64F,
Stride: 1,
},
{ // Transport and Map Symbols
Lo: 0x1F680,
Hi: 0x1F6FF,
Stride: 1,
},
{ // Geometric Shapes Extended
Lo: 0x1F780,
Hi: 0x1F7FF,
Stride: 1,
},
{ // Supplemental Symbols and Pictographs
Lo: 0x1F900,
Hi: 0x1F9FF,
Stride: 1,
},
{ // Symbols and Pictographs Extended-A
Lo: 0x1FA70¸
Hi: 0x1FAFF,
Stride: 1,
},
},
R16: []unicode.Range16{
{ // Zero-width characters
Lo: 0x200B,
Hi: 0x200D,
Stride: 1,
},
{ // Miscellaneous Technical
Lo: 0x2300,
Hi: 0x23FF,
Stride: 1,
},
{ // Miscellaneous Symbols
Lo: 0x2600,
Hi: 0x26FF,
Stride: 1,
},
{ // Dingbats
Lo: 0x2700,
Hi: 0x27BF,
Stride: 1,
},
{ // Miscellaneous Symbols and Arrows
Lo: 0x2B00,
Hi: 0x2BFF,
Stride: 1,
},
{ // Variation Selectors
Lo: 0xFE00,
Hi: 0xFE0F,
Stride: 1,
},
},
}
)
// Emoji represents a Discord emoji.
type Emoji struct {
Name string
ID string
IsAnimated bool
IsCustom bool
}
// APIFormat returns a string that represents the emoji
// in discord API requests.
func (e *Emoji) APIFormat() string {
if e.IsCustom {
return e.Name + ":" + e.ID
} else {
return e.Name
}
}
// MessageFormat returns a string that represents the emoji
// in discord messages.
func (e *Emoji) MessageFormat() string {
if e.IsCustom {
var sb strings.Builder
sb.WriteByte('<')
if e.IsAnimated {
sb.WriteByte('a')
}
sb.WriteByte(':')
sb.WriteString(e.Name)
sb.WriteByte(':')
sb.WriteString(e.ID)
sb.WriteByte('>')
return sb.String()
} else {
return e.Name
}
}
// Parse parses a single Discord emoji. It can handle both
// unicode emoji and custom emoji.
func Parse(s string) (Emoji, bool) {
s = strings.TrimSpace(s)
if isEmoji(s) {
// This string should only contain a single emoji.
// If it has more, return false.
if uniseg.GraphemeClusterCount(s) != 1 {
return Emoji{}, false
}
return Emoji{Name: s}, true
}
if customEmojiRegex.MatchString(s) {
matches := customEmojiRegex.FindStringSubmatch(s)
return Emoji{
Name: matches[2],
ID: matches[3],
IsAnimated: matches[1] == "a",
IsCustom: true,
}, true
}
return Emoji{}, false
}
// isEmoji checks to make sure all the characters in the string
// are within the unicode emoji range table.
func isEmoji(s string) bool {
for _, char := range s {
if !unicode.In(char, unicodeEmojiTable) {
return false
}
}
return true
}