mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-09-13 09:26:23 -07:00
Move Lua plugin loaders to their respective gochan packages
This commit is contained in:
parent
c9abd7a162
commit
9688082339
9 changed files with 543 additions and 472 deletions
32
pkg/config/preload.go
Normal file
32
pkg/config/preload.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
luar "layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
func PreloadModule(l *lua.LState) int {
|
||||
t := l.NewTable()
|
||||
l.SetFuncs(t, map[string]lua.LGFunction{
|
||||
"system_critical_config": func(l *lua.LState) int {
|
||||
l.Push(luar.New(l, &cfg.SystemCriticalConfig))
|
||||
return 1
|
||||
},
|
||||
"site_config": func(l *lua.LState) int {
|
||||
l.Push(luar.New(l, &cfg.SiteConfig))
|
||||
return 1
|
||||
},
|
||||
"board_config": func(l *lua.LState) int {
|
||||
numArgs := l.GetTop()
|
||||
board := ""
|
||||
if numArgs > 0 {
|
||||
board = l.CheckString(1)
|
||||
}
|
||||
l.Push(luar.New(l, GetBoardConfig(board)))
|
||||
return 1
|
||||
},
|
||||
})
|
||||
|
||||
l.Push(t)
|
||||
return 1
|
||||
}
|
59
pkg/events/preload.go
Normal file
59
pkg/events/preload.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package events
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/gochan-org/gochan/pkg/gcplugin/luautil"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
luar "layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
func luaEventRegisterHandlerAdapter(l *lua.LState, fn *lua.LFunction) EventHandler {
|
||||
return func(trigger string, data ...interface{}) error {
|
||||
args := []lua.LValue{
|
||||
luar.New(l, trigger),
|
||||
}
|
||||
for _, i := range data {
|
||||
args = append(args, luar.New(l, i))
|
||||
}
|
||||
l.CallByParam(lua.P{
|
||||
Fn: fn,
|
||||
NRet: 1,
|
||||
// Protect: true,
|
||||
}, args...)
|
||||
errStr := lua.LVAsString(l.Get(-1))
|
||||
if errStr != "" {
|
||||
return errors.New(errStr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func PreloadModule(l *lua.LState) int {
|
||||
t := l.NewTable()
|
||||
l.SetFuncs(t, map[string]lua.LGFunction{
|
||||
"register_event": 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)
|
||||
RegisterEvent(triggers, luaEventRegisterHandlerAdapter(l, fn))
|
||||
return 0
|
||||
},
|
||||
"trigger_event": 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)
|
||||
data = append(data, luautil.LValueToInterface(l, v))
|
||||
}
|
||||
TriggerEvent(trigger, data...)
|
||||
return 0
|
||||
},
|
||||
})
|
||||
l.Push(t)
|
||||
return 1
|
||||
}
|
|
@ -1,18 +1,11 @@
|
|||
package gcplugin
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"plugin"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/Eggbertx/durationutil"
|
||||
"github.com/gochan-org/gochan/pkg/config"
|
||||
"github.com/gochan-org/gochan/pkg/events"
|
||||
"github.com/gochan-org/gochan/pkg/gcsql"
|
||||
|
@ -20,19 +13,12 @@ import (
|
|||
"github.com/gochan-org/gochan/pkg/gcutil"
|
||||
"github.com/gochan-org/gochan/pkg/manage"
|
||||
"github.com/gochan-org/gochan/pkg/posting/uploads"
|
||||
"github.com/gochan-org/gochan/pkg/server/serverutil"
|
||||
"github.com/rs/zerolog"
|
||||
gluahttp "github.com/vadv/gopher-lua-libs/http"
|
||||
|
||||
async "github.com/CuberL/glua-async"
|
||||
luaFilePath "github.com/vadv/gopher-lua-libs/filepath"
|
||||
luaStrings "github.com/vadv/gopher-lua-libs/strings"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
luar "layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
const (
|
||||
tableArgFmt = "invalid value for key %q passed to table, expected %s, got %s"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -54,470 +40,19 @@ func ClosePlugins() {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func lvalueToInterface(l *lua.LState, v lua.LValue) interface{} {
|
||||
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:
|
||||
return lua.LVAsString(v)
|
||||
case lua.LTUserData:
|
||||
l.Push(v)
|
||||
return l.CheckUserData(l.GetTop()).Value
|
||||
default:
|
||||
gcutil.LogError(nil).Caller(1).
|
||||
Interface("lvalue", v).
|
||||
Str("type", lt.String()).
|
||||
Msg("Unrecognized or unsupported Lua type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createLuaLogFunc(which string) lua.LGFunction {
|
||||
return func(l *lua.LState) int {
|
||||
switch which {
|
||||
case "info":
|
||||
l.Push(luar.New(l, gcutil.LogInfo()))
|
||||
case "warn":
|
||||
l.Push(luar.New(l, gcutil.LogWarning()))
|
||||
case "error":
|
||||
numArgs := l.GetTop()
|
||||
if numArgs == 0 {
|
||||
l.Push(luar.New(l, gcutil.LogError(nil)))
|
||||
} else {
|
||||
errVal := l.CheckAny(-1)
|
||||
errI := lvalueToInterface(l, errVal)
|
||||
err := fmt.Errorf("%v", errI)
|
||||
|
||||
l.Push(luar.New(l, gcutil.LogError(err)))
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func luaEventRegisterHandlerAdapter(l *lua.LState, fn *lua.LFunction) events.EventHandler {
|
||||
return func(trigger string, data ...interface{}) error {
|
||||
args := []lua.LValue{
|
||||
luar.New(l, trigger),
|
||||
}
|
||||
for _, i := range data {
|
||||
args = append(args, luar.New(l, i))
|
||||
}
|
||||
l.CallByParam(lua.P{
|
||||
Fn: fn,
|
||||
NRet: 1,
|
||||
// Protect: true,
|
||||
}, args...)
|
||||
errStr := lua.LVAsString(l.Get(-1))
|
||||
if errStr != "" {
|
||||
return errors.New(errStr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func preloadLua() {
|
||||
luaFilePath.Preload(lState)
|
||||
luaStrings.Preload(lState)
|
||||
gluahttp.Preload(lState)
|
||||
async.Init(lState)
|
||||
|
||||
lState.PreloadModule("config", func(l *lua.LState) int {
|
||||
t := l.NewTable()
|
||||
l.SetFuncs(t, map[string]lua.LGFunction{
|
||||
"system_critical_config": func(l *lua.LState) int {
|
||||
l.Push(luar.New(l, config.GetSystemCriticalConfig()))
|
||||
return 1
|
||||
},
|
||||
"site_config": func(l *lua.LState) int {
|
||||
l.Push(luar.New(l, config.GetSiteConfig()))
|
||||
return 1
|
||||
},
|
||||
"board_config": func(l *lua.LState) int {
|
||||
numArgs := l.GetTop()
|
||||
board := ""
|
||||
if numArgs > 0 {
|
||||
board = l.CheckString(1)
|
||||
}
|
||||
l.Push(luar.New(l, config.GetBoardConfig(board)))
|
||||
return 1
|
||||
},
|
||||
})
|
||||
|
||||
l.Push(t)
|
||||
return 1
|
||||
})
|
||||
|
||||
lState.PreloadModule("events", func(l *lua.LState) int {
|
||||
t := l.NewTable()
|
||||
l.SetFuncs(t, map[string]lua.LGFunction{
|
||||
"register_event": 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
|
||||
},
|
||||
"trigger_event": 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)
|
||||
data = append(data, lvalueToInterface(l, v))
|
||||
}
|
||||
events.TriggerEvent(trigger, data...)
|
||||
return 0
|
||||
},
|
||||
})
|
||||
l.Push(t)
|
||||
return 1
|
||||
})
|
||||
|
||||
lState.PreloadModule("gclog", func(l *lua.LState) int {
|
||||
t := l.NewTable()
|
||||
l.SetFuncs(t, map[string]lua.LGFunction{
|
||||
"info_log": createLuaLogFunc("info"),
|
||||
"warn_log": createLuaLogFunc("warn"),
|
||||
"error_log": createLuaLogFunc("error"),
|
||||
})
|
||||
l.Push(t)
|
||||
return 1
|
||||
})
|
||||
|
||||
lState.PreloadModule("gcsql", func(l *lua.LState) int {
|
||||
t := l.NewTable()
|
||||
l.SetFuncs(t, map[string]lua.LGFunction{
|
||||
"query_rows": 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
|
||||
|
||||
},
|
||||
"execute_sql": func(l *lua.LState) int {
|
||||
execStr := l.CheckString(1)
|
||||
execArgsL := l.CheckAny(2)
|
||||
var execArgs []any
|
||||
if execArgsL.Type() != lua.LTNil {
|
||||
table := execArgsL.(*lua.LTable)
|
||||
table.ForEach(func(_, val lua.LValue) {
|
||||
arg := lvalueToInterface(l, val)
|
||||
execArgs = append(execArgs, arg)
|
||||
})
|
||||
}
|
||||
result, err := gcsql.ExecSQL(execStr)
|
||||
|
||||
l.Push(luar.New(l, result))
|
||||
l.Push(luar.New(l, err))
|
||||
return 2
|
||||
},
|
||||
"scan_rows": func(l *lua.LState) int {
|
||||
rows := l.CheckUserData(1).Value.(*sql.Rows)
|
||||
table := l.CheckTable(2)
|
||||
var scanners []any
|
||||
colNames, err := rows.Columns()
|
||||
if err != nil {
|
||||
l.Push(luar.New(l, err))
|
||||
return 1
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
l.Push(lua.LNil)
|
||||
return 1
|
||||
},
|
||||
})
|
||||
|
||||
l.Push(t)
|
||||
return 1
|
||||
})
|
||||
|
||||
lState.PreloadModule("gctemplates", func(l *lua.LState) int {
|
||||
t := l.NewTable()
|
||||
|
||||
l.SetFuncs(t, map[string]lua.LGFunction{
|
||||
"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
|
||||
},
|
||||
"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
|
||||
},
|
||||
"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
|
||||
},
|
||||
})
|
||||
|
||||
l.Push(t)
|
||||
return 1
|
||||
})
|
||||
|
||||
lState.PreloadModule("manage", func(l *lua.LState) int {
|
||||
t := l.NewTable()
|
||||
l.SetFuncs(t, map[string]lua.LGFunction{
|
||||
"ban_ip": func(l *lua.LState) int {
|
||||
now := time.Now()
|
||||
ban := &gcsql.IPBan{
|
||||
IP: l.CheckString(1),
|
||||
}
|
||||
ban.IsActive = true
|
||||
ban.AppealAt = now
|
||||
ban.CanAppeal = true
|
||||
|
||||
durOrNil := l.CheckAny(2)
|
||||
var err error
|
||||
switch durOrNil.Type() {
|
||||
case lua.LTNil:
|
||||
ban.Permanent = true
|
||||
case lua.LTString:
|
||||
var duration time.Duration
|
||||
duration, err = durationutil.ParseLongerDuration(lua.LVAsString(durOrNil))
|
||||
if err != nil {
|
||||
l.Push(luar.New(l, err))
|
||||
return 1
|
||||
}
|
||||
ban.ExpiresAt = time.Now().Add(duration)
|
||||
default:
|
||||
lState.ArgError(2, "Expected string or nil value")
|
||||
}
|
||||
|
||||
ban.Message = l.CheckString(3)
|
||||
|
||||
staff := l.CheckAny(4)
|
||||
switch staff.Type() {
|
||||
case lua.LTString:
|
||||
ban.StaffID, err = gcsql.GetStaffID(lua.LVAsString(staff))
|
||||
if err != nil {
|
||||
l.Push(luar.New(l, err))
|
||||
return 1
|
||||
}
|
||||
case lua.LTNumber:
|
||||
ban.StaffID = int(lua.LVAsNumber(staff))
|
||||
default:
|
||||
l.TypeError(4, staff.Type())
|
||||
}
|
||||
|
||||
if l.GetTop() > 4 {
|
||||
t := l.CheckTable(5)
|
||||
var failed bool
|
||||
t.ForEach(func(keyLV, val lua.LValue) {
|
||||
key := lua.LVAsString(keyLV)
|
||||
valType := val.Type()
|
||||
switch key {
|
||||
case "board":
|
||||
fallthrough
|
||||
case "BoardID":
|
||||
fallthrough
|
||||
case "board_id":
|
||||
switch valType {
|
||||
case lua.LTNil:
|
||||
// global
|
||||
case lua.LTNumber:
|
||||
ban.BoardID = new(int)
|
||||
*ban.BoardID = int(lua.LVAsNumber(val))
|
||||
case lua.LTString:
|
||||
boardDir := lua.LVAsString(val)
|
||||
if boardDir != "" {
|
||||
var id int
|
||||
if id, err = gcsql.GetBoardIDFromDir(boardDir); err != nil {
|
||||
l.Push(luar.New(l, err))
|
||||
return
|
||||
}
|
||||
ban.BoardID = new(int)
|
||||
*ban.BoardID = id
|
||||
}
|
||||
default:
|
||||
failed = true
|
||||
l.Push(lua.LNil)
|
||||
l.RaiseError(tableArgFmt, key, "string, number, or nil", valType)
|
||||
return
|
||||
}
|
||||
case "post_id":
|
||||
fallthrough
|
||||
case "post":
|
||||
fallthrough
|
||||
case "PostID":
|
||||
if valType != lua.LTNumber {
|
||||
failed = true
|
||||
l.Push(lua.LNil)
|
||||
l.RaiseError(tableArgFmt, key, "number", valType)
|
||||
return
|
||||
}
|
||||
case "is_thread_ban":
|
||||
fallthrough
|
||||
case "IsThreadBan":
|
||||
ban.IsThreadBan = lua.LVAsBool(val)
|
||||
case "appeal_after":
|
||||
fallthrough
|
||||
case "AppealAfter":
|
||||
str := lua.LVAsString(val)
|
||||
dur, err := durationutil.ParseLongerDuration(str)
|
||||
if err != nil {
|
||||
l.Push(luar.New(l, err))
|
||||
failed = true
|
||||
return
|
||||
}
|
||||
ban.AppealAt = now.Add(dur)
|
||||
case "can_appeal":
|
||||
fallthrough
|
||||
case "appealable":
|
||||
fallthrough
|
||||
case "CanAppeal":
|
||||
ban.CanAppeal = lua.LVAsBool(val)
|
||||
case "staff_note":
|
||||
fallthrough
|
||||
case "StaffNote":
|
||||
ban.StaffNote = lua.LVAsString(val)
|
||||
}
|
||||
})
|
||||
if failed {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
if ban.StaffID < 1 {
|
||||
l.Push(luar.New(l, errors.New("missing staff key in table")))
|
||||
return 1
|
||||
}
|
||||
ban.IssuedAt = time.Now()
|
||||
err = gcsql.NewIPBan(ban)
|
||||
l.Push(luar.New(l, err))
|
||||
return 1
|
||||
},
|
||||
"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
|
||||
},
|
||||
})
|
||||
|
||||
l.Push(t)
|
||||
return 1
|
||||
})
|
||||
|
||||
lState.PreloadModule("uploads", func(l *lua.LState) int {
|
||||
t := l.NewTable()
|
||||
|
||||
l.SetFuncs(t, map[string]lua.LGFunction{
|
||||
"register_handler": func(l *lua.LState) int {
|
||||
ext := l.CheckString(1)
|
||||
handler := l.CheckFunction(2)
|
||||
uploads.RegisterUploadHandler(ext, func(upload *gcsql.Upload, post *gcsql.Post, board, filePath, thumbPath, catalogThumbPath string, infoEv, accessEv, errEv *zerolog.Event) error {
|
||||
l.CallByParam(lua.P{
|
||||
Fn: handler,
|
||||
NRet: 1,
|
||||
// Protect: true,
|
||||
}, luar.New(l, upload), luar.New(l, post), lua.LString(board), lua.LString(filePath), lua.LString(thumbPath), lua.LString(catalogThumbPath))
|
||||
|
||||
errRet := l.CheckAny(-1)
|
||||
if errRet != nil && errRet.Type() != lua.LTNil {
|
||||
return errors.New(errRet.String())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return 0
|
||||
},
|
||||
"get_thumbnail_ext": func(l *lua.LState) int {
|
||||
fileExt := l.CheckString(1)
|
||||
l.Push(luar.New(l, uploads.GetThumbnailExtension(fileExt)))
|
||||
return 1
|
||||
},
|
||||
"set_thumbnail_ext": func(l *lua.LState) int {
|
||||
fileExt := l.CheckString(1)
|
||||
thumbExt := l.CheckString(2)
|
||||
uploads.SetThumbnailExtension(fileExt, thumbExt)
|
||||
return 0
|
||||
},
|
||||
})
|
||||
|
||||
l.Push(t)
|
||||
return 1
|
||||
})
|
||||
lState.PreloadModule("config", config.PreloadModule)
|
||||
lState.PreloadModule("events", events.PreloadModule)
|
||||
lState.PreloadModule("gclog", gcutil.PreloadModule)
|
||||
lState.PreloadModule("gcsql", gcsql.PreloadModule)
|
||||
lState.PreloadModule("gctemplates", gctemplates.PreloadModule)
|
||||
lState.PreloadModule("manage", manage.PreloadModule)
|
||||
lState.PreloadModule("uploads", uploads.PreloadModule)
|
||||
|
||||
lState.SetGlobal("_GOCHAN_VERSION", lua.LString(config.GetVersion().String()))
|
||||
}
|
||||
|
|
25
pkg/gcplugin/luautil/util.go
Normal file
25
pkg/gcplugin/luautil/util.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package luautil
|
||||
|
||||
import (
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
func LValueToInterface(l *lua.LState, v lua.LValue) interface{} {
|
||||
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:
|
||||
return lua.LVAsString(v)
|
||||
case lua.LTUserData:
|
||||
l.Push(v)
|
||||
return l.CheckUserData(l.GetTop()).Value
|
||||
default:
|
||||
l.RaiseError("Incompatible Lua type")
|
||||
}
|
||||
return nil
|
||||
}
|
94
pkg/gcsql/preload.go
Normal file
94
pkg/gcsql/preload.go
Normal file
|
@ -0,0 +1,94 @@
|
|||
package gcsql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"reflect"
|
||||
|
||||
"github.com/gochan-org/gochan/pkg/gcplugin/luautil"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
luar "layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func PreloadModule(l *lua.LState) int {
|
||||
t := l.NewTable()
|
||||
l.SetFuncs(t, map[string]lua.LGFunction{
|
||||
"query_rows": 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 := luautil.LValueToInterface(l, val)
|
||||
queryArgs = append(queryArgs, arg)
|
||||
})
|
||||
}
|
||||
|
||||
rows, err := QuerySQL(queryStr, queryArgs...)
|
||||
|
||||
l.Push(luar.New(l, rows))
|
||||
l.Push(luar.New(l, err))
|
||||
return 2
|
||||
|
||||
},
|
||||
"execute_sql": func(l *lua.LState) int {
|
||||
execStr := l.CheckString(1)
|
||||
execArgsL := l.CheckAny(2)
|
||||
var execArgs []any
|
||||
if execArgsL.Type() != lua.LTNil {
|
||||
table := execArgsL.(*lua.LTable)
|
||||
table.ForEach(func(_, val lua.LValue) {
|
||||
arg := luautil.LValueToInterface(l, val)
|
||||
execArgs = append(execArgs, arg)
|
||||
})
|
||||
}
|
||||
result, err := ExecSQL(execStr)
|
||||
|
||||
l.Push(luar.New(l, result))
|
||||
l.Push(luar.New(l, err))
|
||||
return 2
|
||||
},
|
||||
"scan_rows": func(l *lua.LState) int {
|
||||
rows := l.CheckUserData(1).Value.(*sql.Rows)
|
||||
table := l.CheckTable(2)
|
||||
var scanners []any
|
||||
colNames, err := rows.Columns()
|
||||
if err != nil {
|
||||
l.Push(luar.New(l, err))
|
||||
return 1
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
l.Push(lua.LNil)
|
||||
return 1
|
||||
},
|
||||
})
|
||||
|
||||
l.Push(t)
|
||||
return 1
|
||||
}
|
54
pkg/gctemplates/preload.go
Normal file
54
pkg/gctemplates/preload.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package gctemplates
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"io"
|
||||
|
||||
"github.com/gochan-org/gochan/pkg/gcplugin/luautil"
|
||||
"github.com/gochan-org/gochan/pkg/server/serverutil"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
luar "layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
func PreloadModule(l *lua.LState) int {
|
||||
t := l.NewTable()
|
||||
|
||||
l.SetFuncs(t, map[string]lua.LGFunction{
|
||||
"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 := LoadTemplate(tmplPaths...)
|
||||
l.Push(luar.New(l, tmpl))
|
||||
l.Push(luar.New(l, err))
|
||||
return 2
|
||||
},
|
||||
"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()] = luautil.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
|
||||
},
|
||||
"parse_template": func(l *lua.LState) int {
|
||||
tmplName := l.CheckString(1)
|
||||
tmplData := l.CheckString(2)
|
||||
tmpl, err := ParseTemplate(tmplName, tmplData)
|
||||
l.Push(luar.New(l, tmpl))
|
||||
l.Push(luar.New(l, err))
|
||||
return 2
|
||||
},
|
||||
})
|
||||
|
||||
l.Push(t)
|
||||
return 1
|
||||
|
||||
}
|
43
pkg/gcutil/preload.go
Normal file
43
pkg/gcutil/preload.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package gcutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gochan-org/gochan/pkg/gcplugin/luautil"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
luar "layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
func createLuaLogFunc(which string) lua.LGFunction {
|
||||
return func(l *lua.LState) int {
|
||||
switch which {
|
||||
case "info":
|
||||
l.Push(luar.New(l, LogInfo()))
|
||||
case "warn":
|
||||
l.Push(luar.New(l, LogWarning()))
|
||||
case "error":
|
||||
numArgs := l.GetTop()
|
||||
if numArgs == 0 {
|
||||
l.Push(luar.New(l, LogError(nil)))
|
||||
} else {
|
||||
errVal := l.CheckAny(-1)
|
||||
errI := luautil.LValueToInterface(l, errVal)
|
||||
err := fmt.Errorf("%v", errI)
|
||||
|
||||
l.Push(luar.New(l, LogError(err)))
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func PreloadModule(l *lua.LState) int {
|
||||
t := l.NewTable()
|
||||
l.SetFuncs(t, map[string]lua.LGFunction{
|
||||
"info_log": createLuaLogFunc("info"),
|
||||
"warn_log": createLuaLogFunc("warn"),
|
||||
"error_log": createLuaLogFunc("error"),
|
||||
})
|
||||
l.Push(t)
|
||||
return 1
|
||||
}
|
180
pkg/manage/preload.go
Normal file
180
pkg/manage/preload.go
Normal file
|
@ -0,0 +1,180 @@
|
|||
package manage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Eggbertx/durationutil"
|
||||
"github.com/gochan-org/gochan/pkg/gcsql"
|
||||
"github.com/rs/zerolog"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
luar "layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
const (
|
||||
tableArgFmt = "invalid value for key %q passed to table, expected %s, got %s"
|
||||
)
|
||||
|
||||
func luaBanIP(l *lua.LState) int {
|
||||
now := time.Now()
|
||||
ban := &gcsql.IPBan{
|
||||
IP: l.CheckString(1),
|
||||
}
|
||||
ban.IsActive = true
|
||||
ban.AppealAt = now
|
||||
ban.CanAppeal = true
|
||||
|
||||
durOrNil := l.CheckAny(2)
|
||||
var err error
|
||||
switch durOrNil.Type() {
|
||||
case lua.LTNil:
|
||||
ban.Permanent = true
|
||||
case lua.LTString:
|
||||
var duration time.Duration
|
||||
duration, err = durationutil.ParseLongerDuration(lua.LVAsString(durOrNil))
|
||||
if err != nil {
|
||||
l.Push(luar.New(l, err))
|
||||
return 1
|
||||
}
|
||||
ban.ExpiresAt = time.Now().Add(duration)
|
||||
default:
|
||||
l.ArgError(2, "Expected string or nil value")
|
||||
}
|
||||
|
||||
ban.Message = l.CheckString(3)
|
||||
|
||||
staff := l.CheckAny(4)
|
||||
switch staff.Type() {
|
||||
case lua.LTString:
|
||||
ban.StaffID, err = gcsql.GetStaffID(lua.LVAsString(staff))
|
||||
if err != nil {
|
||||
l.Push(luar.New(l, err))
|
||||
return 1
|
||||
}
|
||||
case lua.LTNumber:
|
||||
ban.StaffID = int(lua.LVAsNumber(staff))
|
||||
default:
|
||||
l.TypeError(4, staff.Type())
|
||||
}
|
||||
|
||||
if l.GetTop() > 4 {
|
||||
t := l.CheckTable(5)
|
||||
var failed bool
|
||||
t.ForEach(func(keyLV, val lua.LValue) {
|
||||
key := lua.LVAsString(keyLV)
|
||||
valType := val.Type()
|
||||
switch key {
|
||||
case "board":
|
||||
fallthrough
|
||||
case "BoardID":
|
||||
fallthrough
|
||||
case "board_id":
|
||||
switch valType {
|
||||
case lua.LTNil:
|
||||
// global
|
||||
case lua.LTNumber:
|
||||
ban.BoardID = new(int)
|
||||
*ban.BoardID = int(lua.LVAsNumber(val))
|
||||
case lua.LTString:
|
||||
boardDir := lua.LVAsString(val)
|
||||
if boardDir != "" {
|
||||
var id int
|
||||
if id, err = gcsql.GetBoardIDFromDir(boardDir); err != nil {
|
||||
l.Push(luar.New(l, err))
|
||||
return
|
||||
}
|
||||
ban.BoardID = new(int)
|
||||
*ban.BoardID = id
|
||||
}
|
||||
default:
|
||||
failed = true
|
||||
l.Push(lua.LNil)
|
||||
l.RaiseError(tableArgFmt, key, "string, number, or nil", valType)
|
||||
return
|
||||
}
|
||||
case "post_id":
|
||||
fallthrough
|
||||
case "post":
|
||||
fallthrough
|
||||
case "PostID":
|
||||
if valType != lua.LTNumber {
|
||||
failed = true
|
||||
l.Push(lua.LNil)
|
||||
l.RaiseError(tableArgFmt, key, "number", valType)
|
||||
return
|
||||
}
|
||||
case "is_thread_ban":
|
||||
fallthrough
|
||||
case "IsThreadBan":
|
||||
ban.IsThreadBan = lua.LVAsBool(val)
|
||||
case "appeal_after":
|
||||
fallthrough
|
||||
case "AppealAfter":
|
||||
str := lua.LVAsString(val)
|
||||
dur, err := durationutil.ParseLongerDuration(str)
|
||||
if err != nil {
|
||||
l.Push(luar.New(l, err))
|
||||
failed = true
|
||||
return
|
||||
}
|
||||
ban.AppealAt = now.Add(dur)
|
||||
case "can_appeal":
|
||||
fallthrough
|
||||
case "appealable":
|
||||
fallthrough
|
||||
case "CanAppeal":
|
||||
ban.CanAppeal = lua.LVAsBool(val)
|
||||
case "staff_note":
|
||||
fallthrough
|
||||
case "StaffNote":
|
||||
ban.StaffNote = lua.LVAsString(val)
|
||||
}
|
||||
})
|
||||
if failed {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
if ban.StaffID < 1 {
|
||||
l.Push(luar.New(l, errors.New("missing staff key in table")))
|
||||
return 1
|
||||
}
|
||||
ban.IssuedAt = time.Now()
|
||||
err = gcsql.NewIPBan(ban)
|
||||
l.Push(luar.New(l, err))
|
||||
return 1
|
||||
}
|
||||
|
||||
func PreloadModule(l *lua.LState) int {
|
||||
t := l.NewTable()
|
||||
l.SetFuncs(t, map[string]lua.LGFunction{
|
||||
"ban_ip": luaBanIP,
|
||||
"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
|
||||
}
|
||||
RegisterManagePage(actionID, actionTitle, actionPerms, actionJSON, actionHandler)
|
||||
return 0
|
||||
},
|
||||
})
|
||||
|
||||
l.Push(t)
|
||||
return 1
|
||||
}
|
49
pkg/posting/uploads/preload.go
Normal file
49
pkg/posting/uploads/preload.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package uploads
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/gochan-org/gochan/pkg/gcsql"
|
||||
"github.com/rs/zerolog"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
luar "layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
func PreloadModule(l *lua.LState) int {
|
||||
t := l.NewTable()
|
||||
|
||||
l.SetFuncs(t, map[string]lua.LGFunction{
|
||||
"register_handler": func(l *lua.LState) int {
|
||||
ext := l.CheckString(1)
|
||||
handler := l.CheckFunction(2)
|
||||
RegisterUploadHandler(ext, func(upload *gcsql.Upload, post *gcsql.Post, board, filePath, thumbPath, catalogThumbPath string, infoEv, accessEv, errEv *zerolog.Event) error {
|
||||
l.CallByParam(lua.P{
|
||||
Fn: handler,
|
||||
NRet: 1,
|
||||
// Protect: true,
|
||||
}, luar.New(l, upload), luar.New(l, post), lua.LString(board), lua.LString(filePath), lua.LString(thumbPath), lua.LString(catalogThumbPath))
|
||||
|
||||
errRet := l.CheckAny(-1)
|
||||
if errRet != nil && errRet.Type() != lua.LTNil {
|
||||
return errors.New(errRet.String())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return 0
|
||||
},
|
||||
"get_thumbnail_ext": func(l *lua.LState) int {
|
||||
fileExt := l.CheckString(1)
|
||||
l.Push(luar.New(l, GetThumbnailExtension(fileExt)))
|
||||
return 1
|
||||
},
|
||||
"set_thumbnail_ext": func(l *lua.LState) int {
|
||||
fileExt := l.CheckString(1)
|
||||
thumbExt := l.CheckString(2)
|
||||
SetThumbnailExtension(fileExt, thumbExt)
|
||||
return 0
|
||||
},
|
||||
})
|
||||
|
||||
l.Push(t)
|
||||
return 1
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue