2022-06-01 14:17:27 -07:00
|
|
|
package gcplugin
|
|
|
|
|
|
|
|
import (
|
2023-04-22 16:32:32 -07:00
|
|
|
"database/sql"
|
2022-12-30 15:27:19 -08:00
|
|
|
"errors"
|
2023-01-06 20:13:58 -08:00
|
|
|
"html/template"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
2023-04-27 08:57:13 -07:00
|
|
|
"reflect"
|
2022-12-30 15:27:19 -08:00
|
|
|
|
2022-06-01 14:17:27 -07:00
|
|
|
"github.com/gochan-org/gochan/pkg/config"
|
2023-01-05 21:31:28 -08:00
|
|
|
"github.com/gochan-org/gochan/pkg/events"
|
2023-01-06 20:13:58 -08:00
|
|
|
"github.com/gochan-org/gochan/pkg/gcsql"
|
|
|
|
"github.com/gochan-org/gochan/pkg/gctemplates"
|
2022-09-04 14:27:14 -07:00
|
|
|
"github.com/gochan-org/gochan/pkg/gcutil"
|
2023-01-06 20:13:58 -08:00
|
|
|
"github.com/gochan-org/gochan/pkg/manage"
|
|
|
|
"github.com/gochan-org/gochan/pkg/server/serverutil"
|
|
|
|
"github.com/rs/zerolog"
|
2022-09-04 14:27:14 -07:00
|
|
|
|
2023-01-06 20:13:58 -08:00
|
|
|
luaFilePath "github.com/vadv/gopher-lua-libs/filepath"
|
|
|
|
luaStrings "github.com/vadv/gopher-lua-libs/strings"
|
2022-06-01 14:17:27 -07:00
|
|
|
lua "github.com/yuin/gopher-lua"
|
2022-12-30 15:27:19 -08:00
|
|
|
luar "layeh.com/gopher-luar"
|
2022-06-01 14:17:27 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
lState *lua.LState
|
|
|
|
eventPlugins map[string][]*lua.LFunction
|
|
|
|
)
|
|
|
|
|
|
|
|
func initLua() {
|
|
|
|
if lState == nil {
|
|
|
|
lState = lua.NewState()
|
|
|
|
registerLuaFunctions()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-29 16:58:49 -08:00
|
|
|
func ClosePlugins() {
|
|
|
|
if lState != nil {
|
|
|
|
lState.Close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-27 08:57:13 -07:00
|
|
|
type lvalueScanner struct {
|
|
|
|
val lua.LValue
|
|
|
|
state *lua.LState
|
|
|
|
}
|
|
|
|
|
|
|
|
func (lvs *lvalueScanner) Scan(src any) error {
|
|
|
|
typeof := reflect.TypeOf(src)
|
|
|
|
if typeof != nil && typeof.String() == "[]uint8" {
|
|
|
|
src = string(src.([]uint8))
|
|
|
|
}
|
|
|
|
lvs.val = luar.New(lvs.state, src)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-06 20:13:58 -08:00
|
|
|
func lvalueToInterface(l *lua.LState, v lua.LValue) interface{} {
|
2023-01-05 21:31:28 -08:00
|
|
|
lt := v.Type()
|
|
|
|
switch lt {
|
|
|
|
case lua.LTNil:
|
|
|
|
return nil
|
|
|
|
case lua.LTBool:
|
|
|
|
return lua.LVAsBool(v)
|
|
|
|
case lua.LTNumber:
|
|
|
|
return lua.LVAsNumber(v)
|
|
|
|
case lua.LTString:
|
2023-04-27 08:57:13 -07:00
|
|
|
return lua.LVAsString(v)
|
2023-01-06 20:13:58 -08:00
|
|
|
case lua.LTUserData:
|
|
|
|
l.Push(v)
|
|
|
|
return l.CheckUserData(l.GetTop()).Value
|
2023-01-05 21:31:28 -08:00
|
|
|
default:
|
|
|
|
gcutil.LogError(nil).Caller(1).
|
|
|
|
Interface("lvalue", v).
|
|
|
|
Str("type", lt.String()).
|
|
|
|
Msg("Unrecognized or unsupported Lua type")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-06-01 14:17:27 -07:00
|
|
|
func createLuaLogFunc(which string) lua.LGFunction {
|
|
|
|
return func(l *lua.LState) int {
|
|
|
|
switch which {
|
2022-09-18 15:32:07 -07:00
|
|
|
case "info":
|
2022-12-30 15:27:19 -08:00
|
|
|
l.Push(luar.New(l, gcutil.LogInfo()))
|
2022-09-18 15:32:07 -07:00
|
|
|
case "warn":
|
2022-12-30 15:27:19 -08:00
|
|
|
l.Push(luar.New(l, gcutil.LogWarning()))
|
2022-06-01 14:17:27 -07:00
|
|
|
case "error":
|
2022-12-30 15:27:19 -08:00
|
|
|
numArgs := l.GetTop()
|
|
|
|
if numArgs == 0 {
|
|
|
|
l.Push(luar.New(l, gcutil.LogError(nil)))
|
|
|
|
} else {
|
|
|
|
l.Push(luar.New(l, gcutil.LogError(errors.New(l.CheckString(-1)))))
|
|
|
|
}
|
2022-06-01 14:17:27 -07:00
|
|
|
}
|
2022-12-30 15:27:19 -08:00
|
|
|
return 1
|
2022-06-01 14:17:27 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-05 21:31:28 -08:00
|
|
|
func luaEventRegisterHandlerAdapter(l *lua.LState, fn *lua.LFunction) events.EventHandler {
|
|
|
|
return func(trigger string, data ...interface{}) {
|
|
|
|
args := []lua.LValue{
|
|
|
|
luar.New(l, trigger),
|
|
|
|
}
|
|
|
|
for _, i := range data {
|
|
|
|
args = append(args, luar.New(l, i))
|
|
|
|
}
|
|
|
|
l.CallByParam(lua.P{
|
2023-04-22 16:32:32 -07:00
|
|
|
Fn: fn,
|
|
|
|
NRet: 0,
|
|
|
|
// Protect: true,
|
2023-01-05 21:31:28 -08:00
|
|
|
}, args...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-01 14:17:27 -07:00
|
|
|
func registerLuaFunctions() {
|
2023-01-06 20:13:58 -08:00
|
|
|
luaFilePath.Preload(lState)
|
|
|
|
luaStrings.Preload(lState)
|
2022-09-18 15:32:07 -07:00
|
|
|
lState.Register("info_log", createLuaLogFunc("info"))
|
|
|
|
lState.Register("warn_log", createLuaLogFunc("warn"))
|
2022-06-01 14:17:27 -07:00
|
|
|
lState.Register("error_log", createLuaLogFunc("error"))
|
2023-04-22 16:32:32 -07:00
|
|
|
|
2023-01-06 20:13:58 -08:00
|
|
|
lState.Register("system_critical_config", func(l *lua.LState) int {
|
|
|
|
l.Push(luar.New(l, config.GetSystemCriticalConfig()))
|
|
|
|
return 1
|
|
|
|
})
|
|
|
|
lState.Register("site_config", func(l *lua.LState) int {
|
|
|
|
l.Push(luar.New(l, config.GetSiteConfig()))
|
|
|
|
return 1
|
|
|
|
})
|
|
|
|
lState.Register("board_config", func(l *lua.LState) int {
|
|
|
|
l.Push(luar.New(l, config.GetBoardConfig(l.CheckString(1))))
|
|
|
|
return 1
|
|
|
|
})
|
|
|
|
|
2023-04-22 16:32:32 -07:00
|
|
|
lState.Register("db_query", func(l *lua.LState) int {
|
|
|
|
queryStr := l.CheckString(1)
|
|
|
|
queryArgsL := l.CheckAny(2)
|
|
|
|
|
|
|
|
var queryArgs []any
|
|
|
|
if queryArgsL.Type() != lua.LTNil {
|
|
|
|
table := queryArgsL.(*lua.LTable)
|
|
|
|
table.ForEach(func(_ lua.LValue, val lua.LValue) {
|
|
|
|
arg := lvalueToInterface(l, val)
|
|
|
|
queryArgs = append(queryArgs, arg)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
rows, err := gcsql.QuerySQL(queryStr, queryArgs...)
|
|
|
|
|
|
|
|
l.Push(luar.New(l, rows))
|
|
|
|
l.Push(luar.New(l, err))
|
|
|
|
return 2
|
|
|
|
})
|
|
|
|
|
|
|
|
lState.Register("db_scan_rows", func(l *lua.LState) int {
|
|
|
|
rows := l.CheckUserData(1).Value.(*sql.Rows)
|
|
|
|
table := l.CheckTable(2)
|
2023-04-27 08:57:13 -07:00
|
|
|
var scanners []any
|
|
|
|
colNames, err := rows.Columns()
|
2023-04-22 16:32:32 -07:00
|
|
|
if err != nil {
|
|
|
|
l.Push(luar.New(l, err))
|
|
|
|
return 1
|
|
|
|
}
|
2023-04-27 08:57:13 -07:00
|
|
|
|
|
|
|
for _ = range colNames {
|
|
|
|
scanners = append(scanners, &lvalueScanner{state: l})
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = rows.Scan(scanners...); err != nil {
|
|
|
|
l.Push(luar.New(l, err))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
for i, name := range colNames {
|
|
|
|
table.RawSetString(name, scanners[i].(*lvalueScanner).val)
|
|
|
|
}
|
2023-04-22 16:32:32 -07:00
|
|
|
l.Push(lua.LNil)
|
|
|
|
return 1
|
|
|
|
})
|
|
|
|
|
2023-01-05 21:31:28 -08:00
|
|
|
lState.Register("event_register", func(l *lua.LState) int {
|
|
|
|
table := l.CheckTable(-2)
|
|
|
|
var triggers []string
|
|
|
|
table.ForEach(func(i, val lua.LValue) {
|
|
|
|
triggers = append(triggers, val.String())
|
|
|
|
})
|
|
|
|
fn := l.CheckFunction(-1)
|
|
|
|
events.RegisterEvent(triggers, luaEventRegisterHandlerAdapter(l, fn))
|
|
|
|
return 0
|
|
|
|
})
|
|
|
|
lState.Register("event_trigger", func(l *lua.LState) int {
|
|
|
|
trigger := l.CheckString(1)
|
|
|
|
numArgs := l.GetTop()
|
|
|
|
var data []interface{}
|
|
|
|
for i := 2; i <= numArgs; i++ {
|
|
|
|
v := l.CheckAny(i)
|
2023-01-06 20:13:58 -08:00
|
|
|
data = append(data, lvalueToInterface(l, v))
|
2023-01-05 21:31:28 -08:00
|
|
|
}
|
|
|
|
events.TriggerEvent(trigger, data...)
|
|
|
|
return 0
|
|
|
|
})
|
2023-04-22 16:32:32 -07:00
|
|
|
|
2023-01-06 20:13:58 -08:00
|
|
|
lState.Register("register_manage_page", func(l *lua.LState) int {
|
|
|
|
actionID := l.CheckString(1)
|
|
|
|
actionTitle := l.CheckString(2)
|
|
|
|
actionPerms := l.CheckInt(3)
|
|
|
|
actionJSON := l.CheckInt(4)
|
|
|
|
fn := l.CheckFunction(5)
|
|
|
|
actionHandler := func(writer http.ResponseWriter, request *http.Request, staff *gcsql.Staff, wantsJSON bool, infoEv *zerolog.Event, errEv *zerolog.Event) (output interface{}, err error) {
|
|
|
|
if err = l.CallByParam(lua.P{
|
|
|
|
Fn: fn,
|
|
|
|
NRet: 2,
|
|
|
|
Protect: true,
|
|
|
|
}, luar.New(l, writer), luar.New(l, request), luar.New(l, staff), lua.LBool(wantsJSON), luar.New(l, infoEv), luar.New(l, errEv)); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
out := lua.LVAsString(l.Get(-2))
|
|
|
|
errStr := lua.LVAsString(l.Get(-1))
|
|
|
|
if errStr != "" {
|
|
|
|
err = errors.New(errStr)
|
|
|
|
}
|
|
|
|
return out, err
|
|
|
|
}
|
|
|
|
manage.RegisterManagePage(actionID, actionTitle, actionPerms, actionJSON, actionHandler)
|
|
|
|
return 0
|
|
|
|
})
|
|
|
|
lState.Register("load_template", func(l *lua.LState) int {
|
|
|
|
var tmplPaths []string
|
|
|
|
for i := 0; i < l.GetTop(); i++ {
|
|
|
|
tmplPaths = append(tmplPaths, l.CheckString(i+1))
|
|
|
|
}
|
|
|
|
tmpl, err := gctemplates.LoadTemplate(tmplPaths...)
|
|
|
|
l.Push(luar.New(l, tmpl))
|
|
|
|
l.Push(luar.New(l, err))
|
|
|
|
return 2
|
|
|
|
})
|
|
|
|
lState.Register("parse_template", func(l *lua.LState) int {
|
|
|
|
tmplName := l.CheckString(1)
|
|
|
|
tmplData := l.CheckString(2)
|
|
|
|
tmpl, err := gctemplates.ParseTemplate(tmplName, tmplData)
|
|
|
|
l.Push(luar.New(l, tmpl))
|
|
|
|
l.Push(luar.New(l, err))
|
|
|
|
return 2
|
|
|
|
})
|
|
|
|
lState.Register("minify_template", func(l *lua.LState) int {
|
|
|
|
tmplUD := l.CheckUserData(1)
|
|
|
|
tmpl := tmplUD.Value.(*template.Template)
|
|
|
|
dataTable := l.CheckTable(2)
|
|
|
|
data := map[string]interface{}{}
|
|
|
|
dataTable.ForEach(func(l1, l2 lua.LValue) {
|
|
|
|
data[l1.String()] = lvalueToInterface(l, l2)
|
|
|
|
})
|
|
|
|
writer := l.CheckUserData(3).Value.(io.Writer)
|
|
|
|
mediaType := l.CheckString(4)
|
|
|
|
err := serverutil.MinifyTemplate(tmpl, data, writer, mediaType)
|
|
|
|
l.Push(luar.New(l, err))
|
|
|
|
return 1
|
|
|
|
})
|
2022-06-01 14:17:27 -07:00
|
|
|
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
|
|
|
|
}
|
2022-12-29 16:58:49 -08:00
|
|
|
pluginTable := lState.NewTable()
|
2022-06-01 14:17:27 -07:00
|
|
|
pluginTable.ForEach(func(key, val lua.LValue) {
|
|
|
|
keyStr := key.String()
|
|
|
|
fn, ok := val.(*lua.LFunction)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
registerEventFunction(keyStr, fn)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|