diff --git a/cmd/gochan/main.go b/cmd/gochan/main.go index 7bf3bd0a..55b6f6b0 100644 --- a/cmd/gochan/main.go +++ b/cmd/gochan/main.go @@ -10,6 +10,7 @@ import ( "github.com/gochan-org/gochan/pkg/config" "github.com/gochan-org/gochan/pkg/gclog" + "github.com/gochan-org/gochan/pkg/gcplugin" "github.com/gochan-org/gochan/pkg/gcsql" "github.com/gochan-org/gochan/pkg/gctemplates" "github.com/gochan-org/gochan/pkg/posting" @@ -36,6 +37,11 @@ func main() { systemCritical := config.GetSystemCriticalConfig() + err := gcplugin.LoadPlugins(systemCritical.Plugins) + if err != nil { + gclog.Println(stdFatal|gclog.LErrorLog, "Failed loading plugins:", err.Error()) + } + gcsql.ConnectToDB( systemCritical.DBhost, systemCritical.DBtype, systemCritical.DBname, systemCritical.DBusername, systemCritical.DBpassword, systemCritical.DBprefix) @@ -45,7 +51,7 @@ func main() { posting.InitCaptcha() if err := gctemplates.InitTemplates(); err != nil { - gclog.Printf(gclog.LErrorLog|gclog.LStdLog|gclog.LFatal, err.Error()) + gclog.Printf(stdFatal|gclog.LErrorLog, err.Error()) } sc := make(chan os.Signal, 1) diff --git a/go.mod b/go.mod index e8c2c591..cd389f7a 100755 --- a/go.mod +++ b/go.mod @@ -13,6 +13,8 @@ require ( github.com/tdewolff/minify v2.3.6+incompatible github.com/tdewolff/parse v2.3.4+incompatible // indirect github.com/tdewolff/test v1.0.6 // indirect + github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 // indirect golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e golang.org/x/net v0.0.0-20210614182718-04defd469f4e + layeh.com/gopher-luar v1.0.10 // indirect ) diff --git a/go.sum b/go.sum index 91871c8a..e7d3ad1f 100755 --- a/go.sum +++ b/go.sum @@ -1,5 +1,8 @@ github.com/aquilax/tripcode v1.0.0 h1:uPW1T2brVth0t6YiDPlouncHXFGneflsAvkh4zEBN58= github.com/aquilax/tripcode v1.0.0/go.mod h1:Tucn/H6BM/DEmxzj/tnmR7Vs/NV/bgCKo8Wi0yXrtzQ= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/frustra/bbcode v0.0.0-20201127003707-6ef347fbe1c8 h1:sdIsYe6Vv7KIWZWp8KqSeTl+XlF17d+wHCC4lbxFcYs= @@ -20,6 +23,9 @@ github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxp github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ= github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4= github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= +github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ= +github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= @@ -30,6 +36,7 @@ golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+o golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -39,3 +46,5 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +layeh.com/gopher-luar v1.0.10 h1:55b0mpBhN9XSshEd2Nz6WsbYXctyBT35azk4POQNSXo= +layeh.com/gopher-luar v1.0.10/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk= diff --git a/pkg/config/config.go b/pkg/config/config.go index cdb2e73d..02d53e05 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -272,6 +272,7 @@ type SystemCriticalConfig struct { DocumentRoot string `critical:"true"` TemplateDir string `critical:"true"` LogDir string `critical:"true"` + Plugins []string SiteHeaderURL string WebRoot string `description:"The HTTP root appearing in the browser (e.g. '/', 'https://yoursite.net/', etc) that all internal links start with"` @@ -403,3 +404,11 @@ func GetBoardConfig(board string) *BoardConfig { func GetVersion() *GochanVersion { return cfg.Version } + +// SetVersion should (in most cases) only be used for tests, where a config file wouldn't be loaded +func SetVersion(version string) { + if cfg == nil { + cfg = &GochanConfig{} + cfg.Version = ParseVersion(version) + } +} diff --git a/pkg/gcplugin/gcplugin.go b/pkg/gcplugin/gcplugin.go new file mode 100644 index 00000000..fd858753 --- /dev/null +++ b/pkg/gcplugin/gcplugin.go @@ -0,0 +1,75 @@ +package gcplugin + +import ( + "github.com/gochan-org/gochan/pkg/config" + "github.com/gochan-org/gochan/pkg/gclog" + lua "github.com/yuin/gopher-lua" +) + +var ( + lState *lua.LState + eventPlugins map[string][]*lua.LFunction +) + +func initLua() { + if lState == nil { + lState = lua.NewState() + registerLuaFunctions() + } +} + +func createLuaLogFunc(which string) lua.LGFunction { + return func(l *lua.LState) int { + args := []interface{}{} + for v := 1; v <= l.GetTop(); v++ { + args = append(args, l.Get(v)) + } + switch which { + case "access": + gclog.Println(gclog.LAccessLog, args...) + case "error": + gclog.Println(gclog.LErrorLog, args...) + case "staff": + gclog.Println(gclog.LStaffLog, args...) + } + return 0 + } +} + +func registerLuaFunctions() { + lState.Register("access_log", createLuaLogFunc("access")) + lState.Register("error_log", createLuaLogFunc("error")) + lState.Register("staff_log", createLuaLogFunc("staff")) + lState.SetGlobal("_GOCHAN_VERSION", lua.LString(config.GetVersion().String())) +} + +func registerEventFunction(name string, fn *lua.LFunction) { + switch name { + case "onStartup": + fallthrough + case "onPost": + fallthrough + case "onDelete": + eventPlugins[name] = append(eventPlugins[name], fn) + } +} + +func LoadPlugins(paths []string) error { + var err error + for _, pluginPath := range paths { + initLua() + if err = lState.DoFile(pluginPath); err != nil { + return err + } + pluginTable := lState.CheckTable(-1) + pluginTable.ForEach(func(key, val lua.LValue) { + keyStr := key.String() + fn, ok := val.(*lua.LFunction) + if !ok { + return + } + registerEventFunction(keyStr, fn) + }) + } + return nil +} diff --git a/pkg/gcplugin/lua_test.go b/pkg/gcplugin/lua_test.go new file mode 100644 index 00000000..51329094 --- /dev/null +++ b/pkg/gcplugin/lua_test.go @@ -0,0 +1,47 @@ +package gcplugin + +import ( + "testing" + + "github.com/gochan-org/gochan/pkg/config" + "github.com/gochan-org/gochan/pkg/gcsql" + lua "github.com/yuin/gopher-lua" + luar "layeh.com/gopher-luar" +) + +const ( + versionStr = `return _GOCHAN_VERSION +` +) + +func TestVersionFunction(t *testing.T) { + config.SetVersion("3.1") + initLua() + err := lState.DoString(versionStr) + if err != nil { + t.Fatal(err.Error()) + } + testingVersionStr := lState.Get(-1).(lua.LString) + if testingVersionStr != "3.1" { + t.Fatalf("%q != \"3.1\"", testingVersionStr) + } +} + +func TestStructPassing(t *testing.T) { + initLua() + p := &gcsql.Post{ + Name: "Joe Poster", + Email: "joeposter@gmail.com", + MessageHTML: "Message test
", + MessageText: "Message text\n", + } + uData := lState.NewUserData() + uData.Value = p + lState.SetGlobal("post", luar.New(lState, p)) + err := lState.DoString(`print("Receiving post from " .. post["Name"])`) + + if err != nil { + t.Fatal(err.Error()) + } + +}