diff --git a/server/cmd/main.go b/server/cmd/main.go index c784137..83ffaea 100644 --- a/server/cmd/main.go +++ b/server/cmd/main.go @@ -104,14 +104,35 @@ func newServer() (*gameWorldServer, 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() sc, ok := s.scripts[target.ID] s.scriptsMutex.RUnlock() 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 { - sc, err = witch.NewScriptContext() + sc, err = witch.NewScriptContext(tell) if err != nil { return err } diff --git a/server/db/db.go b/server/db/db.go index 43ee980..9de1742 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -29,11 +29,13 @@ type DB interface { // General GetObject(owner, name string) (*Object, error) + GetObjectByID(ID int) (*Object, error) // Defaults Ensure() error // Presence + SessionIDForAvatar(Object) (string, error) AvatarBySessionID(string) (*Object, error) BedroomBySessionID(string) (*Object, error) MoveInto(toMove Object, container Object) error @@ -281,6 +283,26 @@ func (db *pgDB) EndSession(sid string) (err error) { 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) { avatar = &Object{} @@ -358,6 +380,18 @@ func (db *pgDB) Earshot(obj Object) ([]Object, error) { 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) { ctx := context.Background() obj = &Object{} diff --git a/server/witch/header.go b/server/witch/header.go index 872fbdd..b6c627e 100644 --- a/server/witch/header.go +++ b/server/witch/header.go @@ -1,31 +1,51 @@ package witch import ( + "fmt" "log" "github.com/vilmibm/hermeticum/server/db" lua "github.com/yuin/gopher-lua" ) -func hasWrapper(obj db.Object) func(*lua.LState) int { - return func(ls *lua.LState) int { - lv := ls.ToTable(1) - log.Println(lv) - return 0 +const DefaultAvatarScript = ` + has({ + 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 hearsWrapper(obj db.Object) func(*lua.LState) int { - return func(ls *lua.LState) int { - // TODO get handler from _handlers - // TODO call it - // TODO how to get message in here? - - return 0 - } +func witchHas(l *lua.LState) int { + lv := l.ToTable(1) + log.Println(lv) + return 0 } -func does(ls *lua.LState) int { +func witchHears(l *lua.LState) int { + // TODO register handler + handlers := l.GetGlobal("_handlers").(*lua.LTable) + log.Println(handlers) + pattern := l.ToString(1) + cb := l.ToFunction(2) + addHandler(l, "say", pattern, cb) + return 0 +} + +func witchDoes(ls *lua.LState) int { // TODO how to feed events back into the server? // 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? @@ -52,15 +72,19 @@ func does(ls *lua.LState) int { } */ -func addHandler(ls *lua.LState) int { - verb := ls.ToString(1) - pattern := ls.ToString(2) - cb := ls.ToFunction(3) - handlers := ls.GetGlobal("_handlers").(*lua.LTable) - newHandler := ls.NewTable() - newHandler.RawSetString(pattern, cb) - handlerMap := handlers.RawGetString(verb).(*lua.LTable) - handlerMap.RawSetString(verb, newHandler) +func addHandler(l *lua.LState, verb, pattern string, cb *lua.LFunction) int { + handlers := l.GetGlobal("_handlers").(*lua.LTable) + + verbHandlers, ok := handlers.RawGetString(verb).(*lua.LTable) + if !ok { + verbHandlers = l.NewTable() + handlers.RawSetString(verb, verbHandlers) + } + + log.Println("addHandler") + log.Printf("%#v", cb) + + verbHandlers.RawSetString(pattern, cb) return 0 } diff --git a/server/witch/witch.go b/server/witch/witch.go index 29580c8..d7495ee 100644 --- a/server/witch/witch.go +++ b/server/witch/witch.go @@ -1,7 +1,9 @@ package witch import ( + "fmt" "log" + "regexp" "github.com/vilmibm/hermeticum/server/db" lua "github.com/yuin/gopher-lua" @@ -23,12 +25,10 @@ has({ description = "a plate of pasta covered in pomodoro sauce" }) -hears(".*eat.*", function(msg) - does("quivers nervously") -end) - -hears(".*", function(msg) - tellMe(sender().name + " says " + msg) +hears(".*", function() + print(sender.name) + print(msg) + tellMe(msg) 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 { Verb string Rest string @@ -61,10 +56,13 @@ type VerbContext struct { type ScriptContext struct { script string incoming chan VerbContext + tell func(int, string) } -func NewScriptContext() (*ScriptContext, error) { - sc := &ScriptContext{} +func NewScriptContext(tell func(int, string)) (*ScriptContext, error) { + sc := &ScriptContext{ + tell: tell, + } sc.incoming = make(chan VerbContext) go func() { @@ -78,9 +76,17 @@ func NewScriptContext() (*ScriptContext, error) { //sc.script = vc.Target.Script sc.script = dummyScript l = lua.NewState() - l.SetGlobal("has", l.NewFunction(hasWrapper(vc.Target))) - l.SetGlobal("hears", l.NewFunction(hearsWrapper(vc.Target))) + l.SetGlobal("has", l.NewFunction(witchHas)) + l.SetGlobal("hears", l.NewFunction(witchHears)) 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 //if err := l.DoString(obj.Script); 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()) + } + } + }) + }) } }()