Skip to content

Commit 7a3795e

Browse files
we can now add new commands from lua plugins
1 parent 2d45235 commit 7a3795e

File tree

7 files changed

+250
-7
lines changed

7 files changed

+250
-7
lines changed

.golangci.yml

+6
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,9 @@ linters-settings:
2121
- github.com/jackc/pgx/v5/pgxpool
2222
- github.com/jackc/pgx/v5/pgtype
2323
- github.com/yuin/gopher-lua
24+
- github.com/ailncode/gluaxmlpath
25+
- github.com/cjoudrey/gluahttp
26+
- github.com/kohkimakimoto/gluayaml
27+
- github.com/yuin/gluare
28+
- gitlab.com/megalithic-llc/gluasocket
29+
- github.com/layeh/gopher-json

README.md

+47
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,53 @@ milla.send_chatgpt_request(prompt)
702702
milla.query_db(query)
703703
```
704704

705+
```lua
706+
milla.register_cmd(script_path, cmd_name, function_name)
707+
```
708+
709+
Using `register_cmd` we can register a command that will be available to run like the built-in and customs commands.<br/>
710+
Here's an example of how to use it:<br/>
711+
712+
```lua
713+
local milla = require("milla")
714+
local os = require("os")
715+
local json = require("json")
716+
717+
-- setting the proxy value before loading the http module
718+
-- this way, only this script will be using this proxy
719+
os.setenv("ALL_PROXY", "socks5://172.17.0.1:9057")
720+
721+
local http = require("http")
722+
723+
-- this function should be global
724+
-- one string arg that holds all args
725+
-- should only return one string value
726+
function milla_get_ip(arg)
727+
local ip = arg
728+
local response, err = http.request("GET", "http://ip-api.com/json?" .. ip)
729+
if err ~= nil then print(err) end
730+
731+
local json_response, err = json.decode(response.body)
732+
if err ~= nil then print(err) end
733+
for k, v in pairs(json_response) do print(k, v) end
734+
735+
local result = ""
736+
for key, value in pairs(json_response) do
737+
result = result .. key .. ": " .. value .. " -- "
738+
end
739+
740+
return result
741+
end
742+
743+
milla.register_cmd("/plugins/ip.lua", "ip", "milla_get_ip")
744+
```
745+
746+
This will allow us to do:<br/>
747+
748+
```
749+
/terra: /ip 1.1.1.1
750+
```
751+
705752
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/>
706753
707754
More of milla's functionality will be available through milla's lua module over time.<br/>'

main.go

+19-1
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ func getHelpString() string {
220220
helpString += "cmd - run a custom command defined in the customcommands file\n"
221221
helpString += "getall - returns all config options with their value\n"
222222
helpString += "memstats - returns the memory status currently being used\n"
223+
helpString += "load - loads a lua script\n"
224+
helpString += "unload - unloads a lua script\n"
223225

224226
return helpString
225227
}
@@ -535,9 +537,25 @@ func runCommand(
535537
break
536538
}
537539

540+
for key, value := range appConfig.LuaCommands {
541+
if value.Path == args[1] {
542+
appConfig.deleteLuaCommand(key)
543+
544+
break
545+
}
546+
}
547+
538548
appConfig.deleteLstate(args[1])
539549
default:
540-
client.Cmd.Reply(event, errUnknCmd.Error())
550+
_, ok := appConfig.LuaCommands[args[0]]
551+
if !ok {
552+
client.Cmd.Reply(event, errUnknCmd.Error())
553+
554+
break
555+
}
556+
557+
result := RunLuaFunc(args[0], args[1], client, appConfig)
558+
client.Cmd.Reply(event, result)
541559
}
542560
}
543561

plugins.go

+99
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,27 @@ func sendMessageClosure(luaState *lua.LState, client *girc.Client) func(*lua.LSt
198198
}
199199
}
200200

201+
func registerLuaCommand(luaState *lua.LState, appConfig *TomlConfig) func(*lua.LState) int {
202+
return func(luaState *lua.LState) int {
203+
path := luaState.CheckString(1)
204+
commandName := luaState.CheckString(2) //nolint: mnd,gomnd
205+
funcName := luaState.CheckString(3) //nolint: mnd,gomnd
206+
207+
_, ok := appConfig.LuaCommands[commandName]
208+
if ok {
209+
log.Print("command already registered: ", commandName)
210+
211+
return 0
212+
}
213+
214+
appConfig.insertLuaCommand(commandName, path, funcName)
215+
216+
log.Print("registered command: ", commandName, path, funcName)
217+
218+
return 0
219+
}
220+
}
221+
201222
func ircJoinChannelClosure(luaState *lua.LState, client *girc.Client) func(*lua.LState) int {
202223
return func(luaState *lua.LState) int {
203224
channel := luaState.CheckString(1)
@@ -306,6 +327,7 @@ func millaModuleLoaderClosure(luaState *lua.LState, client *girc.Client, appConf
306327
"send_gemini_request": lua.LGFunction(geminiRequestClosure(luaState, appConfig)),
307328
"send_chatgpt_request": lua.LGFunction(chatGPTRequestClosure(luaState, appConfig)),
308329
"query_db": lua.LGFunction(dbQueryClosure(luaState, appConfig)),
330+
"register_cmd": lua.LGFunction(registerLuaCommand(luaState, appConfig)),
309331
}
310332
millaModule := luaState.SetFuncs(luaState.NewTable(), exports)
311333

@@ -378,3 +400,80 @@ func LoadAllPlugins(appConfig *TomlConfig, client *girc.Client) {
378400
go RunScript(scriptPath, client, appConfig)
379401
}
380402
}
403+
404+
func RunLuaFunc(
405+
cmd, args string,
406+
client *girc.Client,
407+
appConfig *TomlConfig,
408+
) string {
409+
luaState := lua.NewState()
410+
defer luaState.Close()
411+
412+
ctx, cancel := context.WithCancel(context.Background())
413+
414+
luaState.SetContext(ctx)
415+
416+
scriptPath := appConfig.LuaCommands[cmd].Path
417+
418+
appConfig.insertLState(scriptPath, luaState, cancel)
419+
420+
luaState.PreloadModule("milla", millaModuleLoaderClosure(luaState, client, appConfig))
421+
gluasocket.Preload(luaState)
422+
gluaxmlpath.Preload(luaState)
423+
luaState.PreloadModule("yaml", gluayaml.Loader)
424+
luaState.PreloadModule("re", gluare.Loader)
425+
luaState.PreloadModule("json", gopherjson.Loader)
426+
427+
var proxyString string
428+
switch proxyString {
429+
case os.Getenv("ALL_PROXY"):
430+
proxyString = os.Getenv("ALL_PROXY")
431+
case os.Getenv("HTTPS_PROXY"):
432+
proxyString = os.Getenv("HTTPS_PROXY")
433+
case os.Getenv("HTTP_PROXY"):
434+
proxyString = os.Getenv("HTTP_PROXY")
435+
case os.Getenv("https_proxy"):
436+
proxyString = os.Getenv("https_proxy")
437+
case os.Getenv("http_proxy"):
438+
proxyString = os.Getenv("http_proxy")
439+
default:
440+
}
441+
442+
proxyTransport := &http.Transport{}
443+
444+
if proxyString != "" {
445+
proxyURL, err := url.Parse(proxyString)
446+
if err != nil {
447+
log.Print(err)
448+
}
449+
proxyTransport.Proxy = http.ProxyURL(proxyURL)
450+
}
451+
452+
luaState.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{Transport: proxyTransport}).Loader)
453+
454+
log.Print("Running lua command script: ", scriptPath)
455+
456+
if err := luaState.DoFile(scriptPath); err != nil {
457+
log.Print(err)
458+
459+
return ""
460+
}
461+
462+
funcLValue := lua.P{
463+
Fn: luaState.GetGlobal(appConfig.LuaCommands[cmd].FuncName),
464+
NRet: 1,
465+
Protect: true,
466+
}
467+
468+
if err := luaState.CallByParam(funcLValue, lua.LString(args)); err != nil {
469+
log.Print("failed running lua command ...")
470+
log.Print(err)
471+
472+
return ""
473+
}
474+
475+
result := luaState.Get(-1)
476+
luaState.Pop(1)
477+
478+
return result.String()
479+
}

plugins/ip.lua

+15-6
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,31 @@ local milla = require("milla")
22
local os = require("os")
33
local json = require("json")
44

5+
-- setting the proxy value before loading the http module
6+
-- this way, only this script will be using this proxy
57
os.setenv("ALL_PROXY", "socks5://172.17.0.1:9057")
68

79
local http = require("http")
810

9-
local function get_ip(arg)
11+
-- this function should be global
12+
-- one string arg that holds all args
13+
-- should only return one string value
14+
function milla_get_ip(arg)
1015
local ip = arg
11-
local response, err = http.request("GET",
12-
"https://getip-api.com/json?" .. ip)
16+
local response, err = http.request("GET", "http://ip-api.com/json?" .. ip)
17+
if err ~= nil then print(err) end
18+
1319
local json_response, err = json.decode(response.body)
20+
if err ~= nil then print(err) end
21+
for k, v in pairs(json_response) do print(k, v) end
1422

1523
local result = ""
16-
for key, value in ipairs(json_response) do
17-
result = result .. key .. ": " .. value .. "\n"
24+
for key, value in pairs(json_response) do
25+
result = result .. key .. ": " .. value .. " -- "
1826
end
1927

2028
return result
2129
end
2230

23-
milla.register_cmd("ip", "get_ip")
31+
-- script_path, command_name, function_name
32+
milla.register_cmd("/plugins/ip.lua", "ip", "milla_get_ip")

plugins/urban.lua

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
local milla = require("milla")
2+
local os = require("os")
3+
local json = require("json")
4+
5+
os.setenv("ALL_PROXY", "socks5://172.17.0.1:9057")
6+
7+
local http = require("http")
8+
9+
function milla_urban(arg)
10+
local user_agent =
11+
"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"
12+
local term = arg
13+
local url = "http://api.urbandictionary.com/v0/define?term=" .. term
14+
local response, err = http.request("GET", url, {
15+
timeout = "10s",
16+
headers = {
17+
["User-Agent"] = user_agent,
18+
["Accept"] = "application/json",
19+
["Host"] = "api.urbandictionary.com",
20+
["Connection"] = "keep-alive",
21+
["Cache-Control"] = "no-cache",
22+
["DNT"] = 1,
23+
["sec-ch-ua-platform"] = "Windows",
24+
["Pragma"] = "no-cache"
25+
}
26+
})
27+
if err ~= nil then print(err) end
28+
print(response.body)
29+
30+
local json_response, err = json.decode(response.body)
31+
if err ~= nil then print(err) end
32+
33+
local result = ""
34+
for k, v in ipairs(json_response["list"]) do
35+
for kk, vv in pairs(v) do print(kk, vv) end
36+
if k == 1 then result = v["definition"] end
37+
end
38+
39+
return result
40+
end
41+
42+
milla.register_cmd("/plugins/urban.lua", "urban", "milla_urban")

types.go

+22
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ type WatchList struct {
3535
Words []string `toml:"watchWords"`
3636
}
3737

38+
type LuaCommand struct {
39+
Path string
40+
FuncName string
41+
}
42+
3843
type TomlConfig struct {
3944
IrcServer string `toml:"ircServer"`
4045
IrcNick string `toml:"ircNick"`
@@ -66,6 +71,7 @@ type TomlConfig struct {
6671
CustomCommands map[string]CustomCommand `toml:"customCommands"`
6772
WatchLists map[string]WatchList `toml:"watchList"`
6873
LuaStates map[string]LuaLstates
74+
LuaCommands map[string]LuaCommand
6975
Temp float64 `toml:"temp"`
7076
RequestTimeout int `toml:"requestTimeout"`
7177
MillaReconnectDelay int `toml:"millaReconnectDelay"`
@@ -112,6 +118,22 @@ func (config *TomlConfig) deleteLstate(name string) {
112118
delete(config.LuaStates, name)
113119
}
114120

121+
func (config *TomlConfig) insertLuaCommand(
122+
cmd, path, name string,
123+
) {
124+
if config.LuaCommands == nil {
125+
config.LuaCommands = make(map[string]LuaCommand)
126+
}
127+
config.LuaCommands[cmd] = LuaCommand{Path: path, FuncName: name}
128+
}
129+
130+
func (config *TomlConfig) deleteLuaCommand(name string) {
131+
if config.LuaCommands == nil {
132+
return
133+
}
134+
delete(config.LuaCommands, name)
135+
}
136+
115137
type AppConfig struct {
116138
Ircd map[string]TomlConfig `toml:"ircd"`
117139
}

0 commit comments

Comments
 (0)