diff options
-rw-r--r-- | .golangci.yml | 3 | ||||
-rw-r--r-- | README.md | 9 | ||||
-rw-r--r-- | go.mod | 1 | ||||
-rw-r--r-- | go.sum | 2 | ||||
-rw-r--r-- | main.go | 22 | ||||
-rw-r--r-- | plugins.go | 179 | ||||
-rw-r--r-- | plugins_test.go | 23 |
7 files changed, 226 insertions, 13 deletions
diff --git a/.golangci.yml b/.golangci.yml index b72d9d8..0b58fca 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,7 +4,7 @@ run: modules-download-mode: readonly allow-parallel-runners: true allow-serial-runners: true - go: '1.21' + go: '1.22.3' linters-settings: depguard: rules: @@ -20,3 +20,4 @@ linters-settings: - github.com/jackc/pgx/v5 - github.com/jackc/pgx/v5/pgxpool - github.com/jackc/pgx/v5/pgtype + - github.com/yuin/gopher-lua @@ -71,7 +71,7 @@ The formatter to use. This tells chroma how to generate the color in the output. - `terminal8` for 8-color terminals - `terminal16` for 16-color terminals - `terminal256` for 256-color terminals -- `terminal16m` for treucolor terminals +- `terminal16m` for truecolor terminals - `html` for HTML output **_NOTE_**: please note that the terminal formatters will increase the size of the IRC event. Depending on the IRC server, this may or may not be a problem. @@ -528,7 +528,12 @@ go build ## FAQ - I end up with color escape sequences getting printed at the end of a line/begging of the next line. What gives? - This is happening because you have reached the message limit on irc which 512 for the event. This practically leaves around 390-400 character left for the message itself. Certain ircds allow for bigger sizes and certain clients might do. But most ircds dont send `linelen` to the clients. In a closed-loop situation where you control everything, as in, the ircd and all the clients(i.e. A private irc network), you can try to increase the `linelen` for the ircd and the client. Please note that the client in this case is girc. You irc client can have its own set of limits too. The 512 limit is hardcoded in girc. You can vendor the build or use the vendored dockerfile, change the hard limit and run milla with an increased limit. Needless to say, you can try to use a chromaFormatter that produces less characters which is basically not using truecolor or `terminal16m`. + This is happening because you have reached the message limit on irc which 512 for the event. This practically leaves around 390-400 character left for the message itself. Certain ircds allow for bigger sizes and certain clients might do. But most ircds don't send `linelen` to the clients. In a closed-loop situation where you control everything, as in, the ircd and all the clients(i.e. A private irc network), you can try to increase the `linelen` for the ircd and the client. Please note that the client in this case is girc. You irc client can have its own set of limits too. The 512 limit is hardcoded in girc. You can vendor the build or use the vendored dockerfile, change the hard limit and run milla with an increased limit. Needless to say, you can try to use a `chromaFormatter` that produces less characters which is basically not using truecolor or `terminal16m`. + +## Resources + +- [OpenRSS](https://openrss.org/) +- [Google Alerts](https://www.google.com/alerts) ## Thanks @@ -9,6 +9,7 @@ require ( github.com/jackc/pgx/v5 v5.5.5 github.com/lrstanley/girc v0.0.0-20240125042120-9add3166e52e github.com/sashabaranov/go-openai v1.19.3 + github.com/yuin/gopher-lua v1.1.1 golang.org/x/net v0.24.0 google.golang.org/api v0.176.1 ) @@ -100,6 +100,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= @@ -83,6 +83,7 @@ type TomlConfig struct { WebIRCGateway string `toml:"webIRCGateway"` WebIRCHostname string `toml:"webIRCHostname"` WebIRCAddress string `toml:"webIRCAddress"` + PluginPath string `toml:"pluginPath"` CustomCommands map[string]CustomCommand `toml:"customCommands"` Temp float64 `toml:"temp"` RequestTimeout int `toml:"requestTimeout"` @@ -353,7 +354,7 @@ func setFieldByName(v reflect.Value, field string, value string) error { func byteToMByte(bytes uint64, ) uint64 { - return bytes / 1024 / 1024 + return bytes / 1024 / 1024 //nolint: mnd,gomnd } func handleCustomCommand( @@ -363,7 +364,8 @@ func handleCustomCommand( appConfig *TomlConfig, ) { log.Println(args) - if len(args) < 2 { + + if len(args) < 2 { //nolint: mnd,gomnd client.Cmd.Reply(event, errNotEnoughArgs.Error()) return @@ -484,7 +486,7 @@ func runCommand( case "help": sendToIRC(client, event, getHelpString(), "noop") case "set": - if len(args) < 3 { + if len(args) < 3 { //nolint: mnd,gomnd client.Cmd.Reply(event, errNotEnoughArgs.Error()) break @@ -495,7 +497,7 @@ func runCommand( client.Cmd.Reply(event, err.Error()) } case "get": - if len(args) < 2 { + if len(args) < 2 { //nolint: mnd,gomnd client.Cmd.Reply(event, errNotEnoughArgs.Error()) break @@ -514,12 +516,12 @@ func runCommand( client.Cmd.Reply(event, fmt.Sprintf("%v", field.Interface())) case "getall": - v := reflect.ValueOf(*appConfig) - t := v.Type() + value := reflect.ValueOf(*appConfig) + t := value.Type() - for i := 0; i < v.NumField(); i++ { + for i := range value.NumField() { field := t.Field(i) - fieldValue := v.Field(i).Interface() + fieldValue := value.Field(i).Interface() client.Cmd.Reply(event, fmt.Sprintf("%s: %v", field.Name, fieldValue)) } case "memstats": @@ -535,7 +537,7 @@ func runCommand( break } - if len(args) < 2 { + if len(args) < 2 { //nolint: mnd,gomnd client.Cmd.Reply(event, errNotEnoughArgs.Error()) break @@ -547,7 +549,7 @@ func runCommand( break } - if len(args) < 2 { + if len(args) < 2 { //nolint: mnd,gomnd client.Cmd.Reply(event, errNotEnoughArgs.Error()) break diff --git a/plugins.go b/plugins.go new file mode 100644 index 0000000..d64f65b --- /dev/null +++ b/plugins.go @@ -0,0 +1,179 @@ +package main + +import ( + "fmt" + "log" + "os" + "reflect" + + lua "github.com/yuin/gopher-lua" +) + +func registerStrucAsLuaMetaTable[T any]( + luaState *lua.LState, + checkStruct func(luaState *lua.LState) *T, + structType T, + metaTableName string, +) { + metaTable := luaState.NewTypeMetatable(metaTableName) + + luaState.SetGlobal(metaTableName, metaTable) + + luaState.SetField( + metaTable, + "new", + luaState.NewFunction( + newStructFunctionFactory(structType, metaTableName), + ), + ) + + var zero [0]T + + luaState.SetField( + metaTable, + "__index", + luaState.SetFuncs( + luaState.NewTable(), + luaTableGenFactory(reflect.TypeOf(zero), + checkStruct, + ), + ), + ) +} + +func newStructFunctionFactory[T any](structType T, metaTableName string) func(*lua.LState) int { + return func(luaState *lua.LState) int { + structInstance := &structType + ud := luaState.NewUserData() + ud.Value = structInstance + luaState.SetMetatable(ud, luaState.GetTypeMetatable(metaTableName)) + luaState.Push(ud) + + return 1 + } +} + +func checkStruct[T any](luaState *lua.LState) *T { + userData := luaState.CheckUserData(1) + if v, ok := userData.Value.(*T); ok { + return v + } + + luaState.ArgError(1, "got wrong struct") + + return nil +} + +func getterSetterFactory[T any]( + fieldName string, + fieldType reflect.Type, + checkStruct func(luaState *lua.LState) *T, +) func(*lua.LState) int { + return func(luaState *lua.LState) int { + genericStruct := checkStruct(luaState) + + structValue := reflect.ValueOf(genericStruct).Elem() + + fieldValue := structValue.FieldByName(fieldName) + + if luaState.GetTop() == 2 { //nolint: mnd,gomnd + switch fieldType.Kind() { + case reflect.String: + fieldValue.SetString(luaState.CheckString(2)) //nolint: mnd,gomnd + case reflect.Float64: + fieldValue.SetFloat(float64(luaState.CheckNumber(2))) //nolint: mnd,gomnd + case reflect.Float32: + fieldValue.SetFloat(float64(luaState.CheckNumber(2))) //nolint: mnd,gomnd + case reflect.Int8: + fieldValue.SetInt(int64(luaState.CheckInt(2))) //nolint: mnd,gomnd + case reflect.Int16: + fieldValue.SetInt(int64(luaState.CheckInt(2))) //nolint: mnd,gomnd + case reflect.Int: + fieldValue.SetInt(int64(luaState.CheckInt(2))) //nolint: mnd,gomnd + case reflect.Int32: + fieldValue.SetInt(int64(luaState.CheckInt(2))) //nolint: mnd,gomnd + case reflect.Int64: + fieldValue.SetInt(int64(luaState.CheckInt(2))) //nolint: mnd,gomnd + case reflect.Bool: + fieldValue.SetBool(luaState.CheckBool(2)) //nolint: mnd,gomnd + default: + log.Print("unsupported type") + } + + return 0 + } + + switch fieldType.Kind() { + case reflect.String: + luaState.Push(lua.LString(fieldValue.Interface().(string))) + case reflect.Float64: + luaState.Push(lua.LNumber(fieldValue.Interface().(float64))) + case reflect.Float32: + luaState.Push(lua.LNumber(fieldValue.Float())) + case reflect.Int8: + luaState.Push(lua.LNumber(fieldValue.Int())) + case reflect.Int16: + luaState.Push(lua.LNumber(fieldValue.Int())) + case reflect.Int: + luaState.Push(lua.LNumber(fieldValue.Int())) + case reflect.Int32: + luaState.Push(lua.LNumber(fieldValue.Int())) + case reflect.Int64: + luaState.Push(lua.LNumber(fieldValue.Int())) + case reflect.Bool: + luaState.Push(lua.LBool(fieldValue.Bool())) + default: + log.Print("unsupported type") + } + + return 1 + } +} + +func luaTableGenFactory[T any]( + structType reflect.Type, + checkStructType func(luaState *lua.LState) *T) map[string]lua.LGFunction { + tableMethods := make(map[string]lua.LGFunction) + + for _, field := range reflect.VisibleFields(structType) { + tableMethods[field.Name] = getterSetterFactory(field.Name, field.Type, checkStructType) + } + + return tableMethods +} + +func RegisterCustomLuaTypes(luaState *lua.LState) { + registerStrucAsLuaMetaTable(luaState, checkStruct, TomlConfig{}, "toml_config") + registerStrucAsLuaMetaTable(luaState, checkStruct, CustomCommand{}, "custom_command") +} + +func returnAllPlugins(pluginPath string) ([]string, error) { + pluginList := make([]string, 0) + + files, err := os.ReadDir(pluginPath) + if err != nil { + return pluginList, fmt.Errorf("Error reading plugins directory: %v", err) + } + + for _, file := range files { + pluginList = append(pluginList, file.Name()) + } + + return pluginList, nil +} + +func LoadAllPlugins(appConfig *TomlConfig) { + luaState := lua.NewState() + defer luaState.Close() + + RegisterCustomLuaTypes(luaState) + + allPlugins, err := returnAllPlugins(appConfig.PluginPath) + if err != nil { + luaState.Close() + + log.Fatal(err) //nolint: gocritic + } + + log.Println(allPlugins) +} diff --git a/plugins_test.go b/plugins_test.go new file mode 100644 index 0000000..c6cd7ef --- /dev/null +++ b/plugins_test.go @@ -0,0 +1,23 @@ +package main + +import ( + "testing" + + lua "github.com/yuin/gopher-lua" +) + +func MetaTableTest(t *testing.T) { + luaState := lua.NewState() + defer luaState.Close() + + RegisterCustomLuaTypes(luaState) + + if err := luaState.DoString(` + config = toml_config.new() + print(config:IrcServer()) + config:TrcServer("irc.freenode.net") + print(config:IrcServer()) + `); err != nil { + t.Fatal(err) + } +} |