Skip to content

Commit

Permalink
automoji: add lua scripting interface
Browse files Browse the repository at this point in the history
  • Loading branch information
frioux committed Jan 7, 2021
1 parent 7be15fd commit 60d1994
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 10 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ require (
github.com/frioux/yaml v0.0.0-20191009230429-1d79e1a4120f
github.com/fsnotify/fsnotify v1.4.9
github.com/google/go-cmp v0.5.4
github.com/hackebrot/go-repr v0.1.0 // indirect
github.com/hackebrot/turtle v0.1.0
github.com/hackebrot/turtle v0.1.1-0.20200616125707-1bb4c277aedd
github.com/headzoo/surf v1.0.1-0.20180909134844-a4a8c16c01dc
github.com/headzoo/ut v0.0.0-20181013193318-a13b5a7a02ca // indirect
github.com/icza/backscanner v0.0.0-20180226082541-a77511ef4f0f
Expand All @@ -23,6 +22,7 @@ require (
github.com/tailscale/hujson v0.0.0-20190930033718-5098e564d9b3
github.com/ulikunitz/xz v0.5.9
github.com/yuin/goldmark v1.3.1
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da
golang.org/x/text v0.3.4
modernc.org/sqlite v1.7.5
)
Expand Down
47 changes: 45 additions & 2 deletions go.sum

Large diffs are not rendered by default.

39 changes: 37 additions & 2 deletions internal/tool/automoji/automoji.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ The following env vars should be set:
* LM_DROPBOX_TOKEN should be set to load a responses.json.
* LM_RESPONSES_PATH should be set to the location of responses.json within dropbox.
* LM_BOT_LUA_PATH should be set to the location of lua to process emoji data within dropbox.
* LM_DISCORD_TOKEN should be set for this to actually function.
Command: auto-emote
Expand All @@ -55,16 +56,30 @@ func Run(args []string, _ io.Reader) error {
matchersMu.Unlock()
return err
}
if lp := os.Getenv("LM_BOT_LUA_PATH"); lp != "" {
luaC, err = loadLua(dbCl, lp)
if err != nil {
matchersMu.Unlock()
return err
}
}
matchersMu.Unlock()
}
if len(args) > 1 {
for _, arg := range args[1:] {
fmt.Println(newEmojiSet(arg).all(0))
es := newEmojiSet(arg)

if err := luaEval(es, luaC); err != nil {
return err
}

fmt.Println(es.all(0))
}
return nil
}

if p := os.Getenv("LM_RESPONSES_PATH"); p != "" {
lp := os.Getenv("LM_BOT_LUA_PATH")
responsesChanged := make(chan struct{})
go func() {
for range responsesChanged {
Expand All @@ -77,6 +92,16 @@ func Run(args []string, _ io.Reader) error {
continue
}
fmt.Fprintf(os.Stderr, "updated matchers (%d)\n", len(matchers))

if lp != "" {
luaC, err = loadLua(dbCl, lp)
if err != nil {
fmt.Fprintln(os.Stderr, err)
matchersMu.Unlock()
continue
}
fmt.Fprintf(os.Stderr, "updated lua (%d bytes)\n", len(luaC))
}
matchersMu.Unlock()
}
}()
Expand Down Expand Up @@ -169,7 +194,12 @@ func emojiAdd(s *discordgo.Session, a *discordgo.MessageReactionAdd) {
return
}

react(s, a.ChannelID, a.MessageID, newEmojiSet(m.Content))
es := newEmojiSet(m.Content)
if err := luaEval(es, luaC); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
react(s, a.ChannelID, a.MessageID, es)
}

var messageCreateTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Expand All @@ -182,6 +212,7 @@ func init() {
}

var matchers []matcher
var luaC string
var matchersMu = &sync.Mutex{}

func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
Expand All @@ -200,6 +231,10 @@ func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
}

es := newEmojiSet(m.Message.Content)
if err := luaEval(es, luaC); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}

lucky := rand.Intn(100) == 0

Expand Down
15 changes: 15 additions & 0 deletions internal/tool/automoji/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package automoji

import (
"encoding/json"
"io/ioutil"
"regexp"

"github.com/frioux/leatherman/internal/dropbox"
Expand Down Expand Up @@ -59,3 +60,17 @@ func loadMatchers(dbCl dropbox.Client, path string) ([]matcher, error) {

return m, nil
}

func loadLua(dbCl dropbox.Client, path string) (string, error) {
r, err := dbCl.Download(path)
if err != nil {
return "", err
}

b, err := ioutil.ReadAll(r)
if err != nil {
return "", err
}

return string(b), err
}
13 changes: 9 additions & 4 deletions internal/tool/automoji/emojiset.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ func newEmojiSet(m string) *emojiSet {
defer matchersMu.Unlock()
matchersMu.Lock()

s := &emojiSet{optional: make(map[string]bool)}
s := &emojiSet{
message: m,
optional: make(map[string]bool),
}

for _, r := range matchers {
if r.MatchString(m) {
Expand All @@ -37,9 +40,9 @@ func newEmojiSet(m string) *emojiSet {
}

m = nonNameRE.ReplaceAllString(m, " ")
words := strings.Split(m, " ")
s.words = strings.Split(m, " ")

for _, word := range words {
for _, word := range s.words {
if word == "" {
continue
}
Expand All @@ -61,7 +64,7 @@ func newEmojiSet(m string) *emojiSet {
}
}
if len(s.optional) == 0 { // since this always finds too much, only use it when nothing is found
for _, word := range words {
for _, word := range s.words {
if es := turtle.Search(word); es != nil {
for _, e := range es {
s.add(e)
Expand All @@ -74,6 +77,8 @@ func newEmojiSet(m string) *emojiSet {
}

type emojiSet struct {
message string
words []string
optional map[string]bool
required []string
}
Expand Down
120 changes: 120 additions & 0 deletions internal/tool/automoji/lua.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package automoji

import (
"regexp"

lua "github.com/yuin/gopher-lua"
)

func registerEmojiSetType(L *lua.LState) {
mt := L.NewTypeMetatable("emojiset")
L.SetGlobal("emojiset", mt)
L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), emojiSetMethods))
}

func setGlobalEmojiSet(L *lua.LState, name string, es *emojiSet) int {
ud := L.NewUserData()
ud.Value = es
L.SetMetatable(ud, L.GetTypeMetatable("emojiset"))
L.SetGlobal(name, ud)
return 1
}

func checkEmojiSet(L *lua.LState) *emojiSet {
ud := L.CheckUserData(1)
if v, ok := ud.Value.(*emojiSet); ok {
return v
}
L.ArgError(1, "emojiSet expected")
return nil
}

var emojiSetMethods = map[string]lua.LGFunction{
"hasoptional": func(L *lua.LState) int {
es := checkEmojiSet(L)
e := L.CheckString(2)
L.Push(lua.LBool(es.optional[e]))
return 1
},
"addoptional": func(L *lua.LState) int {
es := checkEmojiSet(L)
e := L.CheckString(2)
es.optional[e] = true
return 0
},
"removeoptional": func(L *lua.LState) int {
es := checkEmojiSet(L)
e := L.CheckString(2)
delete(es.optional, e)
return 0
},

"hasrequired": func(L *lua.LState) int {
es := checkEmojiSet(L)
e := L.CheckString(2)
for _, s := range es.required {
if s == e {
L.Push(lua.LBool(true))
return 1
}
}
L.Push(lua.LBool(false))
return 1
},
"addrequired": func(L *lua.LState) int {
es := checkEmojiSet(L)
e := L.CheckString(2)
es.required = append(es.required, e)
return 0
},
"removerequired": func(L *lua.LState) int {
es := checkEmojiSet(L)
e := L.CheckString(2)
newRequired := es.required[:0] // share backing array
for _, v := range es.required {
if v == e {
continue
}
newRequired = append(newRequired, v)
}
es.required = newRequired
return 0
},

"message": func(L *lua.LState) int {
es := checkEmojiSet(L)
L.Push(lua.LString(es.message))
return 1
},
"messagematches": func(L *lua.LState) int {
es := checkEmojiSet(L)
e := L.CheckString(2)
re := regexp.MustCompile(e)
L.Push(lua.LBool(re.MatchString(es.message)))
return 1
},

"hasword": func(L *lua.LState) int {
es := checkEmojiSet(L)
e := L.CheckString(2)
for _, s := range es.words {
if s == e {
L.Push(lua.LBool(true))
return 1
}
}
L.Push(lua.LBool(false))
return 1
},
}

func luaEval(es *emojiSet, code string) error {
L := lua.NewState()
defer L.Close()

registerEmojiSetType(L)
registerTurtleType(L)
setGlobalEmojiSet(L, "es", es)

return L.DoString(code)
}
80 changes: 80 additions & 0 deletions internal/tool/automoji/lua_turtle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package automoji

import (
"github.com/hackebrot/turtle"
lua "github.com/yuin/gopher-lua"
)

func registerTurtleType(L *lua.LState) {
mt := L.NewTypeMetatable("turtleemoji")
L.SetGlobal("turtleemoji", mt)
L.SetField(mt, "findbyname", L.NewFunction(findTurtleByName))
L.SetField(mt, "findbychar", L.NewFunction(findTurtleByChar))
L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), turtleMethods))
}

func findTurtleByName(L *lua.LState) int {
name := L.CheckString(1)

ud := L.NewUserData()
ud.Value = turtle.Emojis[name]
L.SetMetatable(ud, L.GetTypeMetatable("turtleemoji"))
L.Push(ud)
return 1
}

func findTurtleByChar(L *lua.LState) int {
name := L.CheckString(1)

ud := L.NewUserData()
ud.Value = turtle.EmojisByChar[name]
L.SetMetatable(ud, L.GetTypeMetatable("turtleemoji"))
L.Push(ud)
return 1
}

func checkTurtle(L *lua.LState) *turtle.Emoji {
ud := L.CheckUserData(1)
if v, ok := ud.Value.(*turtle.Emoji); ok {
return v
}
L.ArgError(1, "turtle.Emoji expected")
return nil
}

var turtleMethods = map[string]lua.LGFunction{
"name": func(L *lua.LState) int {
c := checkTurtle(L)

L.Push(lua.LString(c.Name))

return 1
},
"category": func(L *lua.LState) int {
c := checkTurtle(L)

L.Push(lua.LString(c.Category))

return 1
},
"char": func(L *lua.LState) int {
c := checkTurtle(L)

L.Push(lua.LString(c.Char))

return 1
},
"haskeyword": func(L *lua.LState) int {
c := checkTurtle(L)
e := L.CheckString(2)

for _, s := range c.Keywords {
if s == e {
L.Push(lua.LBool(true))
return 1
}
}
L.Push(lua.LBool(false))
return 1
},
}

0 comments on commit 60d1994

Please sign in to comment.