From 7a3795e44bf030fc0e27c49aea8c32a4457a59f5 Mon Sep 17 00:00:00 2001 From: terminaldweller Date: Sat, 20 Jul 2024 23:43:36 -0400 Subject: we can now add new commands from lua plugins --- .golangci.yml | 6 ++++ README.md | 47 ++++++++++++++++++++++++++ main.go | 20 ++++++++++- plugins.go | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ plugins/ip.lua | 21 ++++++++---- plugins/urban.lua | 42 +++++++++++++++++++++++ types.go | 22 +++++++++++++ 7 files changed, 250 insertions(+), 7 deletions(-) create mode 100644 plugins/urban.lua diff --git a/.golangci.yml b/.golangci.yml index 0b58fca..220bddb 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -21,3 +21,9 @@ linters-settings: - github.com/jackc/pgx/v5/pgxpool - github.com/jackc/pgx/v5/pgtype - github.com/yuin/gopher-lua + - github.com/ailncode/gluaxmlpath + - github.com/cjoudrey/gluahttp + - github.com/kohkimakimoto/gluayaml + - github.com/yuin/gluare + - gitlab.com/megalithic-llc/gluasocket + - github.com/layeh/gopher-json diff --git a/README.md b/README.md index 1c1ea2e..467a5b7 100644 --- a/README.md +++ b/README.md @@ -702,6 +702,53 @@ milla.send_chatgpt_request(prompt) milla.query_db(query) ``` +```lua +milla.register_cmd(script_path, cmd_name, function_name) +``` + +Using `register_cmd` we can register a command that will be available to run like the built-in and customs commands.
+Here's an example of how to use it:
+ +```lua +local milla = require("milla") +local os = require("os") +local json = require("json") + +-- setting the proxy value before loading the http module +-- this way, only this script will be using this proxy +os.setenv("ALL_PROXY", "socks5://172.17.0.1:9057") + +local http = require("http") + +-- this function should be global +-- one string arg that holds all args +-- should only return one string value +function milla_get_ip(arg) + local ip = arg + local response, err = http.request("GET", "http://ip-api.com/json?" .. ip) + if err ~= nil then print(err) end + + local json_response, err = json.decode(response.body) + if err ~= nil then print(err) end + for k, v in pairs(json_response) do print(k, v) end + + local result = "" + for key, value in pairs(json_response) do + result = result .. key .. ": " .. value .. " -- " + end + + return result +end + +milla.register_cmd("/plugins/ip.lua", "ip", "milla_get_ip") +``` + +This will allow us to do:
+ +``` +/terra: /ip 1.1.1.1 +``` + The example rss plugin, accepts a yaml file as input, reeds the provided rss feeds once, extracts the title, author name and link to the resource, sends the feed over to the `#rssfeed` irc channel and exits.
More of milla's functionality will be available through milla's lua module over time.
' diff --git a/main.go b/main.go index 3962a9b..619ea63 100644 --- a/main.go +++ b/main.go @@ -220,6 +220,8 @@ func getHelpString() string { helpString += "cmd - run a custom command defined in the customcommands file\n" helpString += "getall - returns all config options with their value\n" helpString += "memstats - returns the memory status currently being used\n" + helpString += "load - loads a lua script\n" + helpString += "unload - unloads a lua script\n" return helpString } @@ -535,9 +537,25 @@ func runCommand( break } + for key, value := range appConfig.LuaCommands { + if value.Path == args[1] { + appConfig.deleteLuaCommand(key) + + break + } + } + appConfig.deleteLstate(args[1]) default: - client.Cmd.Reply(event, errUnknCmd.Error()) + _, ok := appConfig.LuaCommands[args[0]] + if !ok { + client.Cmd.Reply(event, errUnknCmd.Error()) + + break + } + + result := RunLuaFunc(args[0], args[1], client, appConfig) + client.Cmd.Reply(event, result) } } diff --git a/plugins.go b/plugins.go index 08f2e59..c2775db 100644 --- a/plugins.go +++ b/plugins.go @@ -198,6 +198,27 @@ func sendMessageClosure(luaState *lua.LState, client *girc.Client) func(*lua.LSt } } +func registerLuaCommand(luaState *lua.LState, appConfig *TomlConfig) func(*lua.LState) int { + return func(luaState *lua.LState) int { + path := luaState.CheckString(1) + commandName := luaState.CheckString(2) //nolint: mnd,gomnd + funcName := luaState.CheckString(3) //nolint: mnd,gomnd + + _, ok := appConfig.LuaCommands[commandName] + if ok { + log.Print("command already registered: ", commandName) + + return 0 + } + + appConfig.insertLuaCommand(commandName, path, funcName) + + log.Print("registered command: ", commandName, path, funcName) + + return 0 + } +} + func ircJoinChannelClosure(luaState *lua.LState, client *girc.Client) func(*lua.LState) int { return func(luaState *lua.LState) int { channel := luaState.CheckString(1) @@ -306,6 +327,7 @@ func millaModuleLoaderClosure(luaState *lua.LState, client *girc.Client, appConf "send_gemini_request": lua.LGFunction(geminiRequestClosure(luaState, appConfig)), "send_chatgpt_request": lua.LGFunction(chatGPTRequestClosure(luaState, appConfig)), "query_db": lua.LGFunction(dbQueryClosure(luaState, appConfig)), + "register_cmd": lua.LGFunction(registerLuaCommand(luaState, appConfig)), } millaModule := luaState.SetFuncs(luaState.NewTable(), exports) @@ -378,3 +400,80 @@ func LoadAllPlugins(appConfig *TomlConfig, client *girc.Client) { go RunScript(scriptPath, client, appConfig) } } + +func RunLuaFunc( + cmd, args string, + client *girc.Client, + appConfig *TomlConfig, +) string { + luaState := lua.NewState() + defer luaState.Close() + + ctx, cancel := context.WithCancel(context.Background()) + + luaState.SetContext(ctx) + + scriptPath := appConfig.LuaCommands[cmd].Path + + appConfig.insertLState(scriptPath, luaState, cancel) + + luaState.PreloadModule("milla", millaModuleLoaderClosure(luaState, client, appConfig)) + gluasocket.Preload(luaState) + gluaxmlpath.Preload(luaState) + luaState.PreloadModule("yaml", gluayaml.Loader) + luaState.PreloadModule("re", gluare.Loader) + luaState.PreloadModule("json", gopherjson.Loader) + + var proxyString string + switch proxyString { + case os.Getenv("ALL_PROXY"): + proxyString = os.Getenv("ALL_PROXY") + case os.Getenv("HTTPS_PROXY"): + proxyString = os.Getenv("HTTPS_PROXY") + case os.Getenv("HTTP_PROXY"): + proxyString = os.Getenv("HTTP_PROXY") + case os.Getenv("https_proxy"): + proxyString = os.Getenv("https_proxy") + case os.Getenv("http_proxy"): + proxyString = os.Getenv("http_proxy") + default: + } + + proxyTransport := &http.Transport{} + + if proxyString != "" { + proxyURL, err := url.Parse(proxyString) + if err != nil { + log.Print(err) + } + proxyTransport.Proxy = http.ProxyURL(proxyURL) + } + + luaState.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{Transport: proxyTransport}).Loader) + + log.Print("Running lua command script: ", scriptPath) + + if err := luaState.DoFile(scriptPath); err != nil { + log.Print(err) + + return "" + } + + funcLValue := lua.P{ + Fn: luaState.GetGlobal(appConfig.LuaCommands[cmd].FuncName), + NRet: 1, + Protect: true, + } + + if err := luaState.CallByParam(funcLValue, lua.LString(args)); err != nil { + log.Print("failed running lua command ...") + log.Print(err) + + return "" + } + + result := luaState.Get(-1) + luaState.Pop(1) + + return result.String() +} diff --git a/plugins/ip.lua b/plugins/ip.lua index 57cb0f5..2e9afad 100644 --- a/plugins/ip.lua +++ b/plugins/ip.lua @@ -2,22 +2,31 @@ local milla = require("milla") local os = require("os") local json = require("json") +-- setting the proxy value before loading the http module +-- this way, only this script will be using this proxy os.setenv("ALL_PROXY", "socks5://172.17.0.1:9057") local http = require("http") -local function get_ip(arg) +-- this function should be global +-- one string arg that holds all args +-- should only return one string value +function milla_get_ip(arg) local ip = arg - local response, err = http.request("GET", - "https://getip-api.com/json?" .. ip) + local response, err = http.request("GET", "http://ip-api.com/json?" .. ip) + if err ~= nil then print(err) end + local json_response, err = json.decode(response.body) + if err ~= nil then print(err) end + for k, v in pairs(json_response) do print(k, v) end local result = "" - for key, value in ipairs(json_response) do - result = result .. key .. ": " .. value .. "\n" + for key, value in pairs(json_response) do + result = result .. key .. ": " .. value .. " -- " end return result end -milla.register_cmd("ip", "get_ip") +-- script_path, command_name, function_name +milla.register_cmd("/plugins/ip.lua", "ip", "milla_get_ip") diff --git a/plugins/urban.lua b/plugins/urban.lua new file mode 100644 index 0000000..08c610b --- /dev/null +++ b/plugins/urban.lua @@ -0,0 +1,42 @@ +local milla = require("milla") +local os = require("os") +local json = require("json") + +os.setenv("ALL_PROXY", "socks5://172.17.0.1:9057") + +local http = require("http") + +function milla_urban(arg) + local user_agent = + "Mozilla/5.0 (X11; U; Linux x86_64; pl-PL; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10" + local term = arg + local url = "http://api.urbandictionary.com/v0/define?term=" .. term + local response, err = http.request("GET", url, { + timeout = "10s", + headers = { + ["User-Agent"] = user_agent, + ["Accept"] = "application/json", + ["Host"] = "api.urbandictionary.com", + ["Connection"] = "keep-alive", + ["Cache-Control"] = "no-cache", + ["DNT"] = 1, + ["sec-ch-ua-platform"] = "Windows", + ["Pragma"] = "no-cache" + } + }) + if err ~= nil then print(err) end + print(response.body) + + local json_response, err = json.decode(response.body) + if err ~= nil then print(err) end + + local result = "" + for k, v in ipairs(json_response["list"]) do + for kk, vv in pairs(v) do print(kk, vv) end + if k == 1 then result = v["definition"] end + end + + return result +end + +milla.register_cmd("/plugins/urban.lua", "urban", "milla_urban") diff --git a/types.go b/types.go index 266eccc..d6f3236 100644 --- a/types.go +++ b/types.go @@ -35,6 +35,11 @@ type WatchList struct { Words []string `toml:"watchWords"` } +type LuaCommand struct { + Path string + FuncName string +} + type TomlConfig struct { IrcServer string `toml:"ircServer"` IrcNick string `toml:"ircNick"` @@ -66,6 +71,7 @@ type TomlConfig struct { CustomCommands map[string]CustomCommand `toml:"customCommands"` WatchLists map[string]WatchList `toml:"watchList"` LuaStates map[string]LuaLstates + LuaCommands map[string]LuaCommand Temp float64 `toml:"temp"` RequestTimeout int `toml:"requestTimeout"` MillaReconnectDelay int `toml:"millaReconnectDelay"` @@ -112,6 +118,22 @@ func (config *TomlConfig) deleteLstate(name string) { delete(config.LuaStates, name) } +func (config *TomlConfig) insertLuaCommand( + cmd, path, name string, +) { + if config.LuaCommands == nil { + config.LuaCommands = make(map[string]LuaCommand) + } + config.LuaCommands[cmd] = LuaCommand{Path: path, FuncName: name} +} + +func (config *TomlConfig) deleteLuaCommand(name string) { + if config.LuaCommands == nil { + return + } + delete(config.LuaCommands, name) +} + type AppConfig struct { Ircd map[string]TomlConfig `toml:"ircd"` } -- cgit v1.2.3