lua handlers working via goroutines
parent
5456518feb
commit
ace32f9801
|
@ -104,14 +104,35 @@ func newServer() (*gameWorldServer, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *gameWorldServer) verbHandler(verb, rest string, sender, target db.Object) error {
|
func (s *gameWorldServer) verbHandler(verb, rest string, sender, target db.Object) error {
|
||||||
// I think i should rethink this. sc should maybe be permanent and then they re-create LStates
|
|
||||||
s.scriptsMutex.RLock()
|
s.scriptsMutex.RLock()
|
||||||
sc, ok := s.scripts[target.ID]
|
sc, ok := s.scripts[target.ID]
|
||||||
s.scriptsMutex.RUnlock()
|
s.scriptsMutex.RUnlock()
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
sid, _ := s.db.SessionIDForAvatar(target)
|
||||||
|
tell := func(_ int, _ string) {}
|
||||||
|
if sid != "" {
|
||||||
|
send := s.msgRouter[sid]
|
||||||
|
tell = func(senderID int, msg string) {
|
||||||
|
senderName := "a mysterious stranger"
|
||||||
|
|
||||||
|
sender, err := s.db.GetObjectByID(senderID)
|
||||||
|
if err == nil {
|
||||||
|
senderName = sender.Data["name"]
|
||||||
|
}
|
||||||
|
|
||||||
|
cm := proto.ClientMessage{
|
||||||
|
Type: proto.ClientMessage_OVERHEARD,
|
||||||
|
Text: msg,
|
||||||
|
Speaker: &senderName,
|
||||||
|
}
|
||||||
|
|
||||||
|
send(&cm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
sc, err = witch.NewScriptContext()
|
sc, err = witch.NewScriptContext(tell)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,11 +29,13 @@ type DB interface {
|
||||||
|
|
||||||
// General
|
// General
|
||||||
GetObject(owner, name string) (*Object, error)
|
GetObject(owner, name string) (*Object, error)
|
||||||
|
GetObjectByID(ID int) (*Object, error)
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
Ensure() error
|
Ensure() error
|
||||||
|
|
||||||
// Presence
|
// Presence
|
||||||
|
SessionIDForAvatar(Object) (string, error)
|
||||||
AvatarBySessionID(string) (*Object, error)
|
AvatarBySessionID(string) (*Object, error)
|
||||||
BedroomBySessionID(string) (*Object, error)
|
BedroomBySessionID(string) (*Object, error)
|
||||||
MoveInto(toMove Object, container Object) error
|
MoveInto(toMove Object, container Object) error
|
||||||
|
@ -281,6 +283,26 @@ func (db *pgDB) EndSession(sid string) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *pgDB) SessionIDForAvatar(obj Object) (string, error) {
|
||||||
|
if !obj.Avatar {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
fmt.Printf("%#v", obj)
|
||||||
|
ctx := context.Background()
|
||||||
|
stmt := `SELECT id FROM sessions WHERE account = $1`
|
||||||
|
var sid *string
|
||||||
|
err := db.pool.QueryRow(ctx, stmt, obj.OwnerID).Scan(&sid)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if sid == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return *sid, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (db *pgDB) AvatarBySessionID(sid string) (avatar *Object, err error) {
|
func (db *pgDB) AvatarBySessionID(sid string) (avatar *Object, err error) {
|
||||||
avatar = &Object{}
|
avatar = &Object{}
|
||||||
|
|
||||||
|
@ -358,6 +380,18 @@ func (db *pgDB) Earshot(obj Object) ([]Object, error) {
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *pgDB) GetObjectByID(ID int) (*Object, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
obj := &Object{}
|
||||||
|
stmt := `
|
||||||
|
SELECT id, avatar, data, owner, script
|
||||||
|
FROM objects
|
||||||
|
WHERE id = $1`
|
||||||
|
err := db.pool.QueryRow(ctx, stmt, ID).Scan(
|
||||||
|
&obj.ID, &obj.Avatar, &obj.Data, &obj.OwnerID, &obj.Script)
|
||||||
|
return obj, err
|
||||||
|
}
|
||||||
|
|
||||||
func (db *pgDB) GetObject(owner, name string) (obj *Object, err error) {
|
func (db *pgDB) GetObject(owner, name string) (obj *Object, err error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
obj = &Object{}
|
obj = &Object{}
|
||||||
|
|
|
@ -1,31 +1,51 @@
|
||||||
package witch
|
package witch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/vilmibm/hermeticum/server/db"
|
"github.com/vilmibm/hermeticum/server/db"
|
||||||
lua "github.com/yuin/gopher-lua"
|
lua "github.com/yuin/gopher-lua"
|
||||||
)
|
)
|
||||||
|
|
||||||
func hasWrapper(obj db.Object) func(*lua.LState) int {
|
const DefaultAvatarScript = `
|
||||||
return func(ls *lua.LState) int {
|
has({
|
||||||
lv := ls.ToTable(1)
|
name =
|
||||||
|
})
|
||||||
|
`
|
||||||
|
|
||||||
|
func SetDefaultAvatarScript(obj *db.Object) {
|
||||||
|
hasInvocation := "has({\n"
|
||||||
|
for k, v := range obj.Data {
|
||||||
|
hasInvocation += fmt.Sprintf(`%s = "%s"\n`, k, v)
|
||||||
|
}
|
||||||
|
hasInvocation += "})"
|
||||||
|
|
||||||
|
obj.Script = fmt.Sprintf(`%s
|
||||||
|
hears(".*", function()
|
||||||
|
tellMe(sender, msg)
|
||||||
|
end)
|
||||||
|
`, hasInvocation)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func witchHas(l *lua.LState) int {
|
||||||
|
lv := l.ToTable(1)
|
||||||
log.Println(lv)
|
log.Println(lv)
|
||||||
return 0
|
return 0
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func hearsWrapper(obj db.Object) func(*lua.LState) int {
|
func witchHears(l *lua.LState) int {
|
||||||
return func(ls *lua.LState) int {
|
// TODO register handler
|
||||||
// TODO get handler from _handlers
|
handlers := l.GetGlobal("_handlers").(*lua.LTable)
|
||||||
// TODO call it
|
log.Println(handlers)
|
||||||
// TODO how to get message in here?
|
pattern := l.ToString(1)
|
||||||
|
cb := l.ToFunction(2)
|
||||||
|
addHandler(l, "say", pattern, cb)
|
||||||
return 0
|
return 0
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func does(ls *lua.LState) int {
|
func witchDoes(ls *lua.LState) int {
|
||||||
// TODO how to feed events back into the server?
|
// TODO how to feed events back into the server?
|
||||||
// it needs to behave like an event showing up in Commands stream
|
// it needs to behave like an event showing up in Commands stream
|
||||||
// this handler needs a reference to the gateway which has a channel for sending events that the server will see?
|
// this handler needs a reference to the gateway which has a channel for sending events that the server will see?
|
||||||
|
@ -52,15 +72,19 @@ func does(ls *lua.LState) int {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func addHandler(ls *lua.LState) int {
|
func addHandler(l *lua.LState, verb, pattern string, cb *lua.LFunction) int {
|
||||||
verb := ls.ToString(1)
|
handlers := l.GetGlobal("_handlers").(*lua.LTable)
|
||||||
pattern := ls.ToString(2)
|
|
||||||
cb := ls.ToFunction(3)
|
verbHandlers, ok := handlers.RawGetString(verb).(*lua.LTable)
|
||||||
handlers := ls.GetGlobal("_handlers").(*lua.LTable)
|
if !ok {
|
||||||
newHandler := ls.NewTable()
|
verbHandlers = l.NewTable()
|
||||||
newHandler.RawSetString(pattern, cb)
|
handlers.RawSetString(verb, verbHandlers)
|
||||||
handlerMap := handlers.RawGetString(verb).(*lua.LTable)
|
}
|
||||||
handlerMap.RawSetString(verb, newHandler)
|
|
||||||
|
log.Println("addHandler")
|
||||||
|
log.Printf("%#v", cb)
|
||||||
|
|
||||||
|
verbHandlers.RawSetString(pattern, cb)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package witch
|
package witch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/vilmibm/hermeticum/server/db"
|
"github.com/vilmibm/hermeticum/server/db"
|
||||||
lua "github.com/yuin/gopher-lua"
|
lua "github.com/yuin/gopher-lua"
|
||||||
|
@ -23,12 +25,10 @@ has({
|
||||||
description = "a plate of pasta covered in pomodoro sauce"
|
description = "a plate of pasta covered in pomodoro sauce"
|
||||||
})
|
})
|
||||||
|
|
||||||
hears(".*eat.*", function(msg)
|
hears(".*", function()
|
||||||
does("quivers nervously")
|
print(sender.name)
|
||||||
end)
|
print(msg)
|
||||||
|
tellMe(msg)
|
||||||
hears(".*", function(msg)
|
|
||||||
tellMe(sender().name + " says " + msg)
|
|
||||||
end)
|
end)
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -46,11 +46,6 @@ end)
|
||||||
`
|
`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO figure out channel stuff
|
|
||||||
// TODO figure out how to inject WITCH header
|
|
||||||
// - do i inject from Go or prepend some Lua code?
|
|
||||||
// TODO figure out how the Lua code can affect Go and thus the database
|
|
||||||
|
|
||||||
type VerbContext struct {
|
type VerbContext struct {
|
||||||
Verb string
|
Verb string
|
||||||
Rest string
|
Rest string
|
||||||
|
@ -61,10 +56,13 @@ type VerbContext struct {
|
||||||
type ScriptContext struct {
|
type ScriptContext struct {
|
||||||
script string
|
script string
|
||||||
incoming chan VerbContext
|
incoming chan VerbContext
|
||||||
|
tell func(int, string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewScriptContext() (*ScriptContext, error) {
|
func NewScriptContext(tell func(int, string)) (*ScriptContext, error) {
|
||||||
sc := &ScriptContext{}
|
sc := &ScriptContext{
|
||||||
|
tell: tell,
|
||||||
|
}
|
||||||
sc.incoming = make(chan VerbContext)
|
sc.incoming = make(chan VerbContext)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -78,9 +76,17 @@ func NewScriptContext() (*ScriptContext, error) {
|
||||||
//sc.script = vc.Target.Script
|
//sc.script = vc.Target.Script
|
||||||
sc.script = dummyScript
|
sc.script = dummyScript
|
||||||
l = lua.NewState()
|
l = lua.NewState()
|
||||||
l.SetGlobal("has", l.NewFunction(hasWrapper(vc.Target)))
|
l.SetGlobal("has", l.NewFunction(witchHas))
|
||||||
l.SetGlobal("hears", l.NewFunction(hearsWrapper(vc.Target)))
|
l.SetGlobal("hears", l.NewFunction(witchHears))
|
||||||
l.SetGlobal("_handlers", l.NewTable())
|
l.SetGlobal("_handlers", l.NewTable())
|
||||||
|
l.SetGlobal("tellMe", l.NewFunction(func(l *lua.LState) int {
|
||||||
|
sender := l.GetGlobal("sender").(*lua.LTable)
|
||||||
|
senderID := int(lua.LVAsNumber(sender.RawGetString("ID")))
|
||||||
|
msg := l.ToString(1)
|
||||||
|
log.Printf("%#v %s\n", sender, msg)
|
||||||
|
sc.tell(senderID, msg)
|
||||||
|
return 0
|
||||||
|
}))
|
||||||
// TODO other setup
|
// TODO other setup
|
||||||
//if err := l.DoString(obj.Script); err != nil {
|
//if err := l.DoString(obj.Script); err != nil {
|
||||||
if err = l.DoString(dummyScript); err != nil {
|
if err = l.DoString(dummyScript); err != nil {
|
||||||
|
@ -88,7 +94,32 @@ func NewScriptContext() (*ScriptContext, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO actually trigger the Lua script
|
// TODO check execute permission and bail out potentially
|
||||||
|
|
||||||
|
senderT := l.NewTable()
|
||||||
|
senderT.RawSetString("name", lua.LString(vc.Sender.Data["name"]))
|
||||||
|
senderT.RawSetString("ID", lua.LString(vc.Sender.Data["ID"]))
|
||||||
|
l.SetGlobal("sender", senderT)
|
||||||
|
l.SetGlobal("msg", lua.LString(vc.Rest))
|
||||||
|
|
||||||
|
handlers := l.GetGlobal("_handlers").(*lua.LTable)
|
||||||
|
handlers.ForEach(func(k, v lua.LValue) {
|
||||||
|
if k.String() != vc.Verb {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v.(*lua.LTable).ForEach(func(kk, vv lua.LValue) {
|
||||||
|
pattern := regexp.MustCompile(kk.String())
|
||||||
|
if pattern.MatchString(vc.Rest) {
|
||||||
|
// TODO TODO TODO TODO TODO
|
||||||
|
// this could be a remote code execution vuln; but by being here, I
|
||||||
|
// believe vc.Verb has been effectively validated as "not a pile of
|
||||||
|
// lua code" since it matched a handler.
|
||||||
|
if err = l.DoString(fmt.Sprintf(`_handlers.%s["%s"]()`, vc.Verb, pattern)); err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue