Skip to content

Commit 60d1994

Browse files
committed
automoji: add lua scripting interface
1 parent 7be15fd commit 60d1994

7 files changed

Lines changed: 308 additions & 10 deletions

File tree

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ require (
99
github.com/frioux/yaml v0.0.0-20191009230429-1d79e1a4120f
1010
github.com/fsnotify/fsnotify v1.4.9
1111
github.com/google/go-cmp v0.5.4
12-
github.com/hackebrot/go-repr v0.1.0 // indirect
13-
github.com/hackebrot/turtle v0.1.0
12+
github.com/hackebrot/turtle v0.1.1-0.20200616125707-1bb4c277aedd
1413
github.com/headzoo/surf v1.0.1-0.20180909134844-a4a8c16c01dc
1514
github.com/headzoo/ut v0.0.0-20181013193318-a13b5a7a02ca // indirect
1615
github.com/icza/backscanner v0.0.0-20180226082541-a77511ef4f0f
@@ -23,6 +22,7 @@ require (
2322
github.com/tailscale/hujson v0.0.0-20190930033718-5098e564d9b3
2423
github.com/ulikunitz/xz v0.5.9
2524
github.com/yuin/goldmark v1.3.1
25+
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da
2626
golang.org/x/text v0.3.4
2727
modernc.org/sqlite v1.7.5
2828
)

go.sum

Lines changed: 45 additions & 2 deletions
Large diffs are not rendered by default.

internal/tool/automoji/automoji.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ The following env vars should be set:
3737
3838
* LM_DROPBOX_TOKEN should be set to load a responses.json.
3939
* LM_RESPONSES_PATH should be set to the location of responses.json within dropbox.
40+
* LM_BOT_LUA_PATH should be set to the location of lua to process emoji data within dropbox.
4041
* LM_DISCORD_TOKEN should be set for this to actually function.
4142
4243
Command: auto-emote
@@ -55,16 +56,30 @@ func Run(args []string, _ io.Reader) error {
5556
matchersMu.Unlock()
5657
return err
5758
}
59+
if lp := os.Getenv("LM_BOT_LUA_PATH"); lp != "" {
60+
luaC, err = loadLua(dbCl, lp)
61+
if err != nil {
62+
matchersMu.Unlock()
63+
return err
64+
}
65+
}
5866
matchersMu.Unlock()
5967
}
6068
if len(args) > 1 {
6169
for _, arg := range args[1:] {
62-
fmt.Println(newEmojiSet(arg).all(0))
70+
es := newEmojiSet(arg)
71+
72+
if err := luaEval(es, luaC); err != nil {
73+
return err
74+
}
75+
76+
fmt.Println(es.all(0))
6377
}
6478
return nil
6579
}
6680

6781
if p := os.Getenv("LM_RESPONSES_PATH"); p != "" {
82+
lp := os.Getenv("LM_BOT_LUA_PATH")
6883
responsesChanged := make(chan struct{})
6984
go func() {
7085
for range responsesChanged {
@@ -77,6 +92,16 @@ func Run(args []string, _ io.Reader) error {
7792
continue
7893
}
7994
fmt.Fprintf(os.Stderr, "updated matchers (%d)\n", len(matchers))
95+
96+
if lp != "" {
97+
luaC, err = loadLua(dbCl, lp)
98+
if err != nil {
99+
fmt.Fprintln(os.Stderr, err)
100+
matchersMu.Unlock()
101+
continue
102+
}
103+
fmt.Fprintf(os.Stderr, "updated lua (%d bytes)\n", len(luaC))
104+
}
80105
matchersMu.Unlock()
81106
}
82107
}()
@@ -169,7 +194,12 @@ func emojiAdd(s *discordgo.Session, a *discordgo.MessageReactionAdd) {
169194
return
170195
}
171196

172-
react(s, a.ChannelID, a.MessageID, newEmojiSet(m.Content))
197+
es := newEmojiSet(m.Content)
198+
if err := luaEval(es, luaC); err != nil {
199+
fmt.Fprintln(os.Stderr, err)
200+
return
201+
}
202+
react(s, a.ChannelID, a.MessageID, es)
173203
}
174204

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

184214
var matchers []matcher
215+
var luaC string
185216
var matchersMu = &sync.Mutex{}
186217

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

202233
es := newEmojiSet(m.Message.Content)
234+
if err := luaEval(es, luaC); err != nil {
235+
fmt.Fprintln(os.Stderr, err)
236+
return
237+
}
203238

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

internal/tool/automoji/data.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package automoji
22

33
import (
44
"encoding/json"
5+
"io/ioutil"
56
"regexp"
67

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

6061
return m, nil
6162
}
63+
64+
func loadLua(dbCl dropbox.Client, path string) (string, error) {
65+
r, err := dbCl.Download(path)
66+
if err != nil {
67+
return "", err
68+
}
69+
70+
b, err := ioutil.ReadAll(r)
71+
if err != nil {
72+
return "", err
73+
}
74+
75+
return string(b), err
76+
}

internal/tool/automoji/emojiset.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ func newEmojiSet(m string) *emojiSet {
1717
defer matchersMu.Unlock()
1818
matchersMu.Lock()
1919

20-
s := &emojiSet{optional: make(map[string]bool)}
20+
s := &emojiSet{
21+
message: m,
22+
optional: make(map[string]bool),
23+
}
2124

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

3942
m = nonNameRE.ReplaceAllString(m, " ")
40-
words := strings.Split(m, " ")
43+
s.words = strings.Split(m, " ")
4144

42-
for _, word := range words {
45+
for _, word := range s.words {
4346
if word == "" {
4447
continue
4548
}
@@ -61,7 +64,7 @@ func newEmojiSet(m string) *emojiSet {
6164
}
6265
}
6366
if len(s.optional) == 0 { // since this always finds too much, only use it when nothing is found
64-
for _, word := range words {
67+
for _, word := range s.words {
6568
if es := turtle.Search(word); es != nil {
6669
for _, e := range es {
6770
s.add(e)
@@ -74,6 +77,8 @@ func newEmojiSet(m string) *emojiSet {
7477
}
7578

7679
type emojiSet struct {
80+
message string
81+
words []string
7782
optional map[string]bool
7883
required []string
7984
}

internal/tool/automoji/lua.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package automoji
2+
3+
import (
4+
"regexp"
5+
6+
lua "github.com/yuin/gopher-lua"
7+
)
8+
9+
func registerEmojiSetType(L *lua.LState) {
10+
mt := L.NewTypeMetatable("emojiset")
11+
L.SetGlobal("emojiset", mt)
12+
L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), emojiSetMethods))
13+
}
14+
15+
func setGlobalEmojiSet(L *lua.LState, name string, es *emojiSet) int {
16+
ud := L.NewUserData()
17+
ud.Value = es
18+
L.SetMetatable(ud, L.GetTypeMetatable("emojiset"))
19+
L.SetGlobal(name, ud)
20+
return 1
21+
}
22+
23+
func checkEmojiSet(L *lua.LState) *emojiSet {
24+
ud := L.CheckUserData(1)
25+
if v, ok := ud.Value.(*emojiSet); ok {
26+
return v
27+
}
28+
L.ArgError(1, "emojiSet expected")
29+
return nil
30+
}
31+
32+
var emojiSetMethods = map[string]lua.LGFunction{
33+
"hasoptional": func(L *lua.LState) int {
34+
es := checkEmojiSet(L)
35+
e := L.CheckString(2)
36+
L.Push(lua.LBool(es.optional[e]))
37+
return 1
38+
},
39+
"addoptional": func(L *lua.LState) int {
40+
es := checkEmojiSet(L)
41+
e := L.CheckString(2)
42+
es.optional[e] = true
43+
return 0
44+
},
45+
"removeoptional": func(L *lua.LState) int {
46+
es := checkEmojiSet(L)
47+
e := L.CheckString(2)
48+
delete(es.optional, e)
49+
return 0
50+
},
51+
52+
"hasrequired": func(L *lua.LState) int {
53+
es := checkEmojiSet(L)
54+
e := L.CheckString(2)
55+
for _, s := range es.required {
56+
if s == e {
57+
L.Push(lua.LBool(true))
58+
return 1
59+
}
60+
}
61+
L.Push(lua.LBool(false))
62+
return 1
63+
},
64+
"addrequired": func(L *lua.LState) int {
65+
es := checkEmojiSet(L)
66+
e := L.CheckString(2)
67+
es.required = append(es.required, e)
68+
return 0
69+
},
70+
"removerequired": func(L *lua.LState) int {
71+
es := checkEmojiSet(L)
72+
e := L.CheckString(2)
73+
newRequired := es.required[:0] // share backing array
74+
for _, v := range es.required {
75+
if v == e {
76+
continue
77+
}
78+
newRequired = append(newRequired, v)
79+
}
80+
es.required = newRequired
81+
return 0
82+
},
83+
84+
"message": func(L *lua.LState) int {
85+
es := checkEmojiSet(L)
86+
L.Push(lua.LString(es.message))
87+
return 1
88+
},
89+
"messagematches": func(L *lua.LState) int {
90+
es := checkEmojiSet(L)
91+
e := L.CheckString(2)
92+
re := regexp.MustCompile(e)
93+
L.Push(lua.LBool(re.MatchString(es.message)))
94+
return 1
95+
},
96+
97+
"hasword": func(L *lua.LState) int {
98+
es := checkEmojiSet(L)
99+
e := L.CheckString(2)
100+
for _, s := range es.words {
101+
if s == e {
102+
L.Push(lua.LBool(true))
103+
return 1
104+
}
105+
}
106+
L.Push(lua.LBool(false))
107+
return 1
108+
},
109+
}
110+
111+
func luaEval(es *emojiSet, code string) error {
112+
L := lua.NewState()
113+
defer L.Close()
114+
115+
registerEmojiSetType(L)
116+
registerTurtleType(L)
117+
setGlobalEmojiSet(L, "es", es)
118+
119+
return L.DoString(code)
120+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package automoji
2+
3+
import (
4+
"github.com/hackebrot/turtle"
5+
lua "github.com/yuin/gopher-lua"
6+
)
7+
8+
func registerTurtleType(L *lua.LState) {
9+
mt := L.NewTypeMetatable("turtleemoji")
10+
L.SetGlobal("turtleemoji", mt)
11+
L.SetField(mt, "findbyname", L.NewFunction(findTurtleByName))
12+
L.SetField(mt, "findbychar", L.NewFunction(findTurtleByChar))
13+
L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), turtleMethods))
14+
}
15+
16+
func findTurtleByName(L *lua.LState) int {
17+
name := L.CheckString(1)
18+
19+
ud := L.NewUserData()
20+
ud.Value = turtle.Emojis[name]
21+
L.SetMetatable(ud, L.GetTypeMetatable("turtleemoji"))
22+
L.Push(ud)
23+
return 1
24+
}
25+
26+
func findTurtleByChar(L *lua.LState) int {
27+
name := L.CheckString(1)
28+
29+
ud := L.NewUserData()
30+
ud.Value = turtle.EmojisByChar[name]
31+
L.SetMetatable(ud, L.GetTypeMetatable("turtleemoji"))
32+
L.Push(ud)
33+
return 1
34+
}
35+
36+
func checkTurtle(L *lua.LState) *turtle.Emoji {
37+
ud := L.CheckUserData(1)
38+
if v, ok := ud.Value.(*turtle.Emoji); ok {
39+
return v
40+
}
41+
L.ArgError(1, "turtle.Emoji expected")
42+
return nil
43+
}
44+
45+
var turtleMethods = map[string]lua.LGFunction{
46+
"name": func(L *lua.LState) int {
47+
c := checkTurtle(L)
48+
49+
L.Push(lua.LString(c.Name))
50+
51+
return 1
52+
},
53+
"category": func(L *lua.LState) int {
54+
c := checkTurtle(L)
55+
56+
L.Push(lua.LString(c.Category))
57+
58+
return 1
59+
},
60+
"char": func(L *lua.LState) int {
61+
c := checkTurtle(L)
62+
63+
L.Push(lua.LString(c.Char))
64+
65+
return 1
66+
},
67+
"haskeyword": func(L *lua.LState) int {
68+
c := checkTurtle(L)
69+
e := L.CheckString(2)
70+
71+
for _, s := range c.Keywords {
72+
if s == e {
73+
L.Push(lua.LBool(true))
74+
return 1
75+
}
76+
}
77+
L.Push(lua.LBool(false))
78+
return 1
79+
},
80+
}

0 commit comments

Comments
 (0)