Skip to content

Commit

Permalink
we can now add new commands from lua plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
terminaldweller committed Jul 21, 2024
1 parent 2d45235 commit 7a3795e
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 7 deletions.
6 changes: 6 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/>'
Expand Down
20 changes: 19 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}
}

Expand Down
99 changes: 99 additions & 0 deletions plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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()
}
21 changes: 15 additions & 6 deletions plugins/ip.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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")
42 changes: 42 additions & 0 deletions plugins/urban.lua
Original file line number Diff line number Diff line change
@@ -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")
22 changes: 22 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -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"`
}
Expand Down

0 comments on commit 7a3795e

Please sign in to comment.