aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorterminaldweller <devi@terminaldweller.com>2024-07-21 03:43:36 +0000
committerterminaldweller <devi@terminaldweller.com>2024-07-21 03:43:36 +0000
commit7a3795e44bf030fc0e27c49aea8c32a4457a59f5 (patch)
tree37d8ce89ad9a63688be5ee9bfa6632bde9a418ca
parentWIP (diff)
downloadmilla-7a3795e44bf030fc0e27c49aea8c32a4457a59f5.tar.gz
milla-7a3795e44bf030fc0e27c49aea8c32a4457a59f5.zip
we can now add new commands from lua plugins
-rw-r--r--.golangci.yml6
-rw-r--r--README.md47
-rw-r--r--main.go20
-rw-r--r--plugins.go99
-rw-r--r--plugins/ip.lua21
-rw-r--r--plugins/urban.lua42
-rw-r--r--types.go22
7 files changed, 250 insertions, 7 deletions
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.<br/>
+Here's an example of how to use it:<br/>
+
+```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:<br/>
+
+```
+/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.<br/>
More of milla's functionality will be available through milla's lua module over time.<br/>'
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"`
}