Rework emote parsing to properly handle wrapped emotes

Reworked the emote parsing to properly handle "wrapped" emotes.  A
wrapped emote is one that is wrapped in colons or square braces (eg,
:Kappa: or [Kappa]).  This allows emotes to be input without a space
between them, like is required with non-wrapped emotes.

A new configuration setting has been added to only allow parsing of
wrapped emotes: "WrappedEmotesOnly".  This defaults to False as to not
break current configurations.

The emote autocompletion will only insert non-wrapped emotes.  Setting
"WrappedEmotesOnly" configuration value to True will not change this.

Lastly, made the bare-word emote parsing a bit cleaner by removing a for
loop in favor of a map lookup with a check.  This should in theory be
more efficient.

This change is in response to #111.  The issue should not be considered
resolved until the autocompletion handles the new setting correctly.
This commit is contained in:
Zorchenhimer 2020-04-19 12:26:27 -04:00
parent ce2150f719
commit fab56e39ea
4 changed files with 75 additions and 9 deletions

View File

@ -10,8 +10,12 @@ import (
type EmotesMap map[string]string
var Emotes EmotesMap
var WrappedEmotesOnly bool = false
var reStripStatic = regexp.MustCompile(`^(\\|/)?static`)
var (
reStripStatic = regexp.MustCompile(`^(\\|/)?static`)
reWrappedEmotes = regexp.MustCompile(`[:\[][^\s:\/\\\?=#\]\[]+[:\]]`)
)
func init() {
Emotes = NewEmotesMap()
@ -40,7 +44,6 @@ func (em EmotesMap) Add(fullpath string) EmotesMap {
}
em[code] = fullpath
//fmt.Printf("Added emote %s at path %q\n", code, fullpath)
return em
}
@ -48,23 +51,33 @@ func EmoteToHtml(file, title string) string {
return fmt.Sprintf(`<img src="%s" height="28px" title="%s" />`, file, title)
}
// Used with a regexp.ReplaceAllStringFunc() call. Needs to lookup the value as it
// cannot be passed in with the regex function call.
func emoteToHmtl2(key string) string {
key = strings.Trim(key, ":[]")
if val, ok := Emotes[key]; ok {
return fmt.Sprintf(`<img src="%s" height="28px" title="%s" />`, val, key)
}
return key
}
func ParseEmotesArray(words []string) []string {
newWords := []string{}
for _, word := range words {
// make :emote: and [emote] valid for replacement.
wordTrimmed := strings.Trim(word, ":[]")
found := false
for key, val := range Emotes {
if key == wordTrimmed {
newWords = append(newWords, EmoteToHtml(val, key))
if !WrappedEmotesOnly {
if val, ok := Emotes[word]; ok {
newWords = append(newWords, EmoteToHtml(val, word))
found = true
}
}
if !found {
word = reWrappedEmotes.ReplaceAllStringFunc(word, emoteToHmtl2)
newWords = append(newWords, word)
}
}
return newWords
}

View File

@ -14,10 +14,18 @@ var data_good = map[string]string{
":two:": `<img src="/emotes/two.png" height="28px" title="two" />`,
":three:": `<img src="/emotes/three.gif" height="28px" title="three" />`,
":one::one:": `<img src="/emotes/one.png" height="28px" title="one" /><img src="/emotes/one.png" height="28px" title="one" />`,
":one:one:": `<img src="/emotes/one.png" height="28px" title="one" />one:`,
"oneone": "oneone",
"one:one:": `one<img src="/emotes/one.png" height="28px" title="one" />`,
"[one]": `<img src="/emotes/one.png" height="28px" title="one" />`,
"[two]": `<img src="/emotes/two.png" height="28px" title="two" />`,
"[three]": `<img src="/emotes/three.gif" height="28px" title="three" />`,
"[one][one]": `<img src="/emotes/one.png" height="28px" title="one" /><img src="/emotes/one.png" height="28px" title="one" />`,
"[one]one": `<img src="/emotes/one.png" height="28px" title="one" />one`,
":one: two [three]": `<img src="/emotes/one.png" height="28px" title="one" /> <img src="/emotes/two.png" height="28px" title="two" /> <img src="/emotes/three.gif" height="28px" title="three" />`,
"nope one what": `nope <img src="/emotes/one.png" height="28px" title="one" /> what`,
@ -25,6 +33,34 @@ var data_good = map[string]string{
"nope [three] what": `nope <img src="/emotes/three.gif" height="28px" title="three" /> what`,
}
var data_wrapped = map[string]string{
"one": `one`,
"two": `two`,
"three": `three`,
":one:": `<img src="/emotes/one.png" height="28px" title="one" />`,
":two:": `<img src="/emotes/two.png" height="28px" title="two" />`,
":three:": `<img src="/emotes/three.gif" height="28px" title="three" />`,
":one::one:": `<img src="/emotes/one.png" height="28px" title="one" /><img src="/emotes/one.png" height="28px" title="one" />`,
":one:one:": `<img src="/emotes/one.png" height="28px" title="one" />one:`,
"oneone": "oneone",
"one:one:": `one<img src="/emotes/one.png" height="28px" title="one" />`,
"[one]": `<img src="/emotes/one.png" height="28px" title="one" />`,
"[two]": `<img src="/emotes/two.png" height="28px" title="two" />`,
"[three]": `<img src="/emotes/three.gif" height="28px" title="three" />`,
"[one][one]": `<img src="/emotes/one.png" height="28px" title="one" /><img src="/emotes/one.png" height="28px" title="one" />`,
"[one]one": `<img src="/emotes/one.png" height="28px" title="one" />one`,
":one: two [three]": `<img src="/emotes/one.png" height="28px" title="one" /> two <img src="/emotes/three.gif" height="28px" title="three" />`,
"nope one what": `nope one what`,
"nope :two: what": `nope <img src="/emotes/two.png" height="28px" title="two" /> what`,
"nope [three] what": `nope <img src="/emotes/three.gif" height="28px" title="three" /> what`,
}
func TestMain(m *testing.M) {
Emotes = map[string]string{
"one": "/emotes/one.png",
@ -42,3 +78,12 @@ func TestEmotes_ParseEmotes(t *testing.T) {
}
}
}
func TestEmotes_ParseEmotes_WrappedOnly(t *testing.T) {
for input, expected := range data_good {
got := ParseEmotes(input)
if got != expected {
t.Errorf("%s failed to parse into %q. Received: %q", input, expected, got)
}
}
}

View File

@ -40,6 +40,8 @@ type Settings struct {
RoomAccessPin string // The current pin
NewPin bool // Auto generate a new pin on start. Overwrites RoomAccessPin if set.
WrappedEmotesOnly bool // only allow "wrapped" emotes. eg :Kappa: and [Kappa] but not Kappa
// Rate limiting stuff, in seconds
RateLimitChat time.Duration
RateLimitNick time.Duration
@ -128,6 +130,11 @@ func LoadSettings(filename string) (*Settings, error) {
s.RateLimitDuplicate = 30
}
if s.WrappedEmotesOnly {
common.LogInfoln("Only allowing wrapped emotes")
common.WrappedEmotesOnly = true
}
// Print this stuff before we multiply it by time.Second
common.LogInfof("RateLimitChat: %v", s.RateLimitChat)
common.LogInfof("RateLimitNick: %v", s.RateLimitNick)

View File

@ -14,5 +14,6 @@
"RateLimitColor": 60,
"RateLimitAuth": 5,
"RateLimitDuplicate": 30,
"NoCache": false
"NoCache": false,
"WrappedEmotesOnly": false
}