feat: 初始提交 - Code Server Bridge完整实现
- OAuth认证系统(Gitea + Lua扩展) - Git自动化操作(本地/SSH远程) - 实时进度WebSocket推送 - 现代化Tab界面UI - Cobra CLI命令行(init/version/serve) - 完整构建系统(Makefile + Taskfile) - UPX压缩支持(体积减少70%)
This commit is contained in:
393
pkg/lua/api.go
Normal file
393
pkg/lua/api.go
Normal file
@@ -0,0 +1,393 @@
|
||||
package lua
|
||||
|
||||
import (
|
||||
"cs-bridge/pkg/httpclient"
|
||||
"cs-bridge/pkg/logger"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
// RegisterHTTPModule registers the http module in Lua
|
||||
func RegisterHTTPModule(L *lua.LState) {
|
||||
httpMod := L.NewTable()
|
||||
|
||||
// http.get(url, headers)
|
||||
httpMod.RawSetString("get", L.NewFunction(luaHTTPGet))
|
||||
|
||||
// http.post(url, data, headers)
|
||||
httpMod.RawSetString("post", L.NewFunction(luaHTTPPost))
|
||||
|
||||
// http.put(url, data, headers)
|
||||
httpMod.RawSetString("put", L.NewFunction(luaHTTPPut))
|
||||
|
||||
// http.delete(url, headers)
|
||||
httpMod.RawSetString("delete", L.NewFunction(luaHTTPDelete))
|
||||
|
||||
L.SetGlobal("http", httpMod)
|
||||
}
|
||||
|
||||
// luaHTTPGet implements http.get(url, headers)
|
||||
func luaHTTPGet(L *lua.LState) int {
|
||||
url := L.CheckString(1)
|
||||
headers := L.OptTable(2, nil)
|
||||
|
||||
req := httpclient.Default.R()
|
||||
|
||||
// Set headers if provided
|
||||
if headers != nil {
|
||||
headers.ForEach(func(key, value lua.LValue) {
|
||||
req.SetHeader(key.String(), value.String())
|
||||
})
|
||||
}
|
||||
|
||||
resp, err := req.Get(url)
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
result := parseHTTPResponse(L, resp)
|
||||
L.Push(result)
|
||||
return 1
|
||||
}
|
||||
|
||||
// luaHTTPPost implements http.post(url, data, headers)
|
||||
func luaHTTPPost(L *lua.LState) int {
|
||||
url := L.CheckString(1)
|
||||
data := L.CheckTable(2)
|
||||
headers := L.OptTable(3, nil)
|
||||
|
||||
req := httpclient.Default.R()
|
||||
|
||||
// Convert Lua table to map for request body
|
||||
bodyMap := luaTableToMap(data)
|
||||
req.SetBody(bodyMap)
|
||||
req.SetHeader("Content-Type", "application/json")
|
||||
|
||||
// Set additional headers if provided
|
||||
if headers != nil {
|
||||
headers.ForEach(func(key, value lua.LValue) {
|
||||
req.SetHeader(key.String(), value.String())
|
||||
})
|
||||
}
|
||||
|
||||
resp, err := req.Post(url)
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
result := parseHTTPResponse(L, resp)
|
||||
L.Push(result)
|
||||
return 1
|
||||
}
|
||||
|
||||
// luaHTTPPut implements http.put(url, data, headers)
|
||||
func luaHTTPPut(L *lua.LState) int {
|
||||
url := L.CheckString(1)
|
||||
data := L.CheckTable(2)
|
||||
headers := L.OptTable(3, nil)
|
||||
|
||||
req := httpclient.Default.R()
|
||||
|
||||
bodyMap := luaTableToMap(data)
|
||||
req.SetBody(bodyMap)
|
||||
req.SetHeader("Content-Type", "application/json")
|
||||
|
||||
if headers != nil {
|
||||
headers.ForEach(func(key, value lua.LValue) {
|
||||
req.SetHeader(key.String(), value.String())
|
||||
})
|
||||
}
|
||||
|
||||
resp, err := req.Put(url)
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
result := parseHTTPResponse(L, resp)
|
||||
L.Push(result)
|
||||
return 1
|
||||
}
|
||||
|
||||
// luaHTTPDelete implements http.delete(url, headers)
|
||||
func luaHTTPDelete(L *lua.LState) int {
|
||||
url := L.CheckString(1)
|
||||
headers := L.OptTable(2, nil)
|
||||
|
||||
req := httpclient.Default.R()
|
||||
|
||||
if headers != nil {
|
||||
headers.ForEach(func(key, value lua.LValue) {
|
||||
req.SetHeader(key.String(), value.String())
|
||||
})
|
||||
}
|
||||
|
||||
resp, err := req.Delete(url)
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
result := parseHTTPResponse(L, resp)
|
||||
L.Push(result)
|
||||
return 1
|
||||
}
|
||||
|
||||
// parseHTTPResponse parses HTTP response and returns Lua table
|
||||
func parseHTTPResponse(L *lua.LState, resp *resty.Response) lua.LValue {
|
||||
// Read response body
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
logger.GetLogger().Error(fmt.Sprintf("读取响应体失败: %v", err))
|
||||
// Return error table
|
||||
result := L.NewTable()
|
||||
result.RawSetString("error", lua.LString(err.Error()))
|
||||
result.RawSetString("status", lua.LNumber(resp.StatusCode()))
|
||||
return result
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Log response for debugging
|
||||
logger.GetLogger().Debug(fmt.Sprintf("HTTP响应 [%d]: %s", resp.StatusCode(), string(body)))
|
||||
|
||||
// Try to parse as JSON first
|
||||
var jsonData any
|
||||
if err := json.Unmarshal(body, &jsonData); err == nil {
|
||||
logger.GetLogger().Debug(fmt.Sprintf("JSON解析成功,类型: %T, 值: %+v", jsonData, jsonData))
|
||||
result := GoToLua(L, jsonData)
|
||||
logger.GetLogger().Debug(fmt.Sprintf("转换为Lua后类型: %s", result.Type().String()))
|
||||
|
||||
// Try to access a test field if it's a table
|
||||
if tbl, ok := result.(*lua.LTable); ok {
|
||||
testVal := tbl.RawGetString("access_token")
|
||||
logger.GetLogger().Debug(fmt.Sprintf("测试访问access_token: %s (type: %s)", testVal.String(), testVal.Type().String()))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// If not JSON, return as string in a table
|
||||
logger.GetLogger().Debug(fmt.Sprintf("响应不是有效JSON: %v", err))
|
||||
result := L.NewTable()
|
||||
result.RawSetString("body", lua.LString(string(body)))
|
||||
result.RawSetString("status", lua.LNumber(resp.StatusCode()))
|
||||
return result
|
||||
}
|
||||
|
||||
// luaTableToMap converts a Lua table to a Go map
|
||||
func luaTableToMap(t *lua.LTable) map[string]any {
|
||||
result := make(map[string]any)
|
||||
t.ForEach(func(key, value lua.LValue) {
|
||||
result[key.String()] = luaValueToGo(value)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
// luaValueToGo converts a Lua value to a Go value
|
||||
func luaValueToGo(lv lua.LValue) any {
|
||||
switch v := lv.(type) {
|
||||
case *lua.LNilType:
|
||||
return nil
|
||||
case lua.LBool:
|
||||
return bool(v)
|
||||
case lua.LNumber:
|
||||
return float64(v)
|
||||
case lua.LString:
|
||||
return string(v)
|
||||
case *lua.LTable:
|
||||
// Check if it's an array or map
|
||||
if v.Len() > 0 {
|
||||
// Array
|
||||
arr := make([]any, 0, v.Len())
|
||||
for i := 1; i <= v.Len(); i++ {
|
||||
arr = append(arr, luaValueToGo(v.RawGetInt(i)))
|
||||
}
|
||||
return arr
|
||||
}
|
||||
// Map
|
||||
m := make(map[string]any)
|
||||
v.ForEach(func(key, value lua.LValue) {
|
||||
m[key.String()] = luaValueToGo(value)
|
||||
})
|
||||
return m
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterJSONModule registers the json module in Lua
|
||||
func RegisterJSONModule(L *lua.LState) {
|
||||
jsonMod := L.NewTable()
|
||||
|
||||
// json.encode(table)
|
||||
jsonMod.RawSetString("encode", L.NewFunction(luaJSONEncode))
|
||||
|
||||
// json.decode(string)
|
||||
jsonMod.RawSetString("decode", L.NewFunction(luaJSONDecode))
|
||||
|
||||
L.SetGlobal("json", jsonMod)
|
||||
}
|
||||
|
||||
// luaJSONEncode implements json.encode(value)
|
||||
func luaJSONEncode(L *lua.LState) int {
|
||||
value := L.CheckAny(1)
|
||||
|
||||
goValue := luaValueToGo(value)
|
||||
jsonBytes, err := json.Marshal(goValue)
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
L.Push(lua.LString(string(jsonBytes)))
|
||||
return 1
|
||||
}
|
||||
|
||||
// luaJSONDecode implements json.decode(jsonString)
|
||||
func luaJSONDecode(L *lua.LState) int {
|
||||
jsonStr := L.CheckString(1)
|
||||
|
||||
var data any
|
||||
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
L.Push(GoToLua(L, data))
|
||||
return 1
|
||||
}
|
||||
|
||||
// RegisterLogModule registers the log module in Lua
|
||||
func RegisterLogModule(L *lua.LState) {
|
||||
logMod := L.NewTable()
|
||||
|
||||
logMod.RawSetString("debug", L.NewFunction(luaLogDebug))
|
||||
logMod.RawSetString("info", L.NewFunction(luaLogInfo))
|
||||
logMod.RawSetString("warn", L.NewFunction(luaLogWarn))
|
||||
logMod.RawSetString("error", L.NewFunction(luaLogError))
|
||||
|
||||
L.SetGlobal("log", logMod)
|
||||
}
|
||||
|
||||
// luaLogDebug implements log.debug(message, ...)
|
||||
func luaLogDebug(L *lua.LState) int {
|
||||
msg := formatLogMessage(L)
|
||||
logger.GetLogger().Debug(msg)
|
||||
return 0
|
||||
}
|
||||
|
||||
// luaLogInfo implements log.info(message, ...)
|
||||
func luaLogInfo(L *lua.LState) int {
|
||||
msg := formatLogMessage(L)
|
||||
logger.GetLogger().Info(msg)
|
||||
return 0
|
||||
}
|
||||
|
||||
// luaLogWarn implements log.warn(message, ...)
|
||||
func luaLogWarn(L *lua.LState) int {
|
||||
msg := formatLogMessage(L)
|
||||
logger.GetLogger().Warn(msg)
|
||||
return 0
|
||||
}
|
||||
|
||||
// luaLogError implements log.error(message, ...)
|
||||
func luaLogError(L *lua.LState) int {
|
||||
msg := formatLogMessage(L)
|
||||
logger.GetLogger().Error(msg)
|
||||
return 0
|
||||
}
|
||||
|
||||
// formatLogMessage formats log message from Lua arguments
|
||||
func formatLogMessage(L *lua.LState) string {
|
||||
n := L.GetTop()
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return L.CheckString(1)
|
||||
}
|
||||
|
||||
// Format string with arguments
|
||||
format := L.CheckString(1)
|
||||
args := make([]any, n-1)
|
||||
for i := 2; i <= n; i++ {
|
||||
args[i-2] = luaValueToGo(L.Get(i))
|
||||
}
|
||||
return fmt.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
// RegisterUtilModule registers utility functions in Lua
|
||||
func RegisterUtilModule(L *lua.LState) {
|
||||
utilMod := L.NewTable()
|
||||
|
||||
// base64.encode(string)
|
||||
utilMod.RawSetString("base64_encode", L.NewFunction(luaBase64Encode))
|
||||
|
||||
// base64.decode(string)
|
||||
utilMod.RawSetString("base64_decode", L.NewFunction(luaBase64Decode))
|
||||
|
||||
// url.encode(string)
|
||||
utilMod.RawSetString("url_encode", L.NewFunction(luaURLEncode))
|
||||
|
||||
// url.decode(string)
|
||||
utilMod.RawSetString("url_decode", L.NewFunction(luaURLDecode))
|
||||
|
||||
L.SetGlobal("util", utilMod)
|
||||
}
|
||||
|
||||
// luaBase64Encode implements base64_encode(str)
|
||||
func luaBase64Encode(L *lua.LState) int {
|
||||
str := L.CheckString(1)
|
||||
encoded := base64.StdEncoding.EncodeToString([]byte(str))
|
||||
L.Push(lua.LString(encoded))
|
||||
return 1
|
||||
}
|
||||
|
||||
// luaBase64Decode implements base64_decode(str)
|
||||
func luaBase64Decode(L *lua.LState) int {
|
||||
str := L.CheckString(1)
|
||||
decoded, err := base64.StdEncoding.DecodeString(str)
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
L.Push(lua.LString(string(decoded)))
|
||||
return 1
|
||||
}
|
||||
|
||||
// luaURLEncode implements url_encode(str)
|
||||
func luaURLEncode(L *lua.LState) int {
|
||||
str := L.CheckString(1)
|
||||
encoded := url.QueryEscape(str)
|
||||
L.Push(lua.LString(encoded))
|
||||
return 1
|
||||
}
|
||||
|
||||
// luaURLDecode implements url_decode(str)
|
||||
func luaURLDecode(L *lua.LState) int {
|
||||
str := L.CheckString(1)
|
||||
decoded, err := url.QueryUnescape(str)
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
L.Push(lua.LString(decoded))
|
||||
return 1
|
||||
}
|
||||
Reference in New Issue
Block a user