aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Dockerfile4
-rw-r--r--Dockerfile_scratch_vendored14
-rw-r--r--README.md139
-rw-r--r--defaults.go135
-rw-r--r--go.mod6
-rw-r--r--go.sum43
-rw-r--r--main.go152
-rw-r--r--plugins.go64
-rw-r--r--plugins/hello.lua5
-rw-r--r--plugins/repology.lua33
-rw-r--r--rss.go2
-rw-r--r--types.go164
-rw-r--r--utils.go7
14 files changed, 500 insertions, 270 deletions
diff --git a/.gitignore b/.gitignore
index 3b735ec..fb5df45 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,5 @@
# Go workspace file
go.work
+
+,*
diff --git a/Dockerfile b/Dockerfile
index 0cd1f23..550fd41 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,7 +6,7 @@ COPY *.go /milla/
RUN go build
FROM alpine:3.21
-ENV HOME /home/user
+ENV HOME=/home/user
RUN set -eux; \
adduser -u 1001 -D -h "$HOME" user; \
mkdir "$HOME/.irssi"; \
@@ -14,4 +14,4 @@ RUN set -eux; \
COPY --from=builder /milla/milla "$HOME/milla"
RUN chown user:user "$HOME/milla"
USER user
-ENTRYPOINT ["home/user/milla"]
+ENTRYPOINT ["/home/user/milla"]
diff --git a/Dockerfile_scratch_vendored b/Dockerfile_scratch_vendored
new file mode 100644
index 0000000..34d6b07
--- /dev/null
+++ b/Dockerfile_scratch_vendored
@@ -0,0 +1,14 @@
+FROM golang:1.23-alpine3.21 AS builder
+WORKDIR /milla
+COPY go.sum go.mod /milla/
+COPY vendor /milla/vendor
+COPY *.go /milla/
+RUN CGO_ENABLED=0 go build
+
+FROM alpine:3.21 AS cert
+RUN apk add --no-cache ca-certificates
+
+FROM scratch
+COPY --from=cert /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
+COPY --from=builder /milla/milla "/milla"
+ENTRYPOINT ["/milla"]
diff --git a/README.md b/README.md
index 0318549..5c9660d 100644
--- a/README.md
+++ b/README.md
@@ -25,68 +25,76 @@ The bot will see a chat prompt as a command if the message begins with `botnick:
An example is provided under `config-example.toml`. Please note that all the config options are specific to one instance which is defined by `ircd.nameofyourinstance`.<br/>
-| Option | Description |
-| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| ircServer | The address for the IRC server to connect to |
-| ircNick | The nick the bot should use |
-| enableSasl | Whether to use SASL for authentication |
-| ircSaslUser | The SASL username |
-| ircSaslPass | The SASL password for SASL plain authentication. Can also be passed as and environment variable |
-| Endpoint | The address for the Ollama chat endpoint |
-| model | The name of the model to use |
-| chromaStyle | The style to use for syntax highlighting done by [chroma](https://github.com/alecthomas/chroma). This is basically what's called a "theme" |
-| chromaFormatter | The formatter to use. This tells chroma how to generate the color in the output. The supported options are:<br><br>- `noop` for no syntax highlighting<br>- `terminal` for 8-color terminals<br>- `terminal8` for 8-color terminals<br>- `terminal16` for 16-color terminals<br>- `terminal256` for 256-color terminals<br>- `terminal16m` for truecolor terminals<br>- `html` for HTML output<br><br>**_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. |
-| provider | Which LLM provider to use. The supported options are:<br><br>- [ollama](https://github.com/ollama/ollama)<br>- chatgpt<br>- gemini<br>- [openrouter](https://openrouter.ai/)<br> |
-| apikey | The apikey to use for the LLM provider. Can also be passed as and environment variable |
-| clientCertPath | The path to the client certificate to use for client cert authentication |
-| serverPass | The password to use for the IRC server the bot is trying to connect to if the server has a password. Can also be passed as and environment variable |
-| bind | Which address to bind to for the IRC server |
-| requestTimeout | The timeout for requests made to the LLM provider |
-| millaReconnectDelay | How much to wait before reconnecting to the IRC server |
-| ircPort | Which port to connect to for the IRC server |
-| keepAlive | |
-| memoryLimit | How many conversations to keep in memory for a model |
-| pingDelay | Ping delay for the IRC server |
-| pingTimeout | Ping timeout for the IRC server |
-| skipTLSVerify | Skip verifying the IRC server's TLS certificate. This only makes sense if you are trying to connect to an IRC server with a self-signed certificate |
-| useTLS | Whether to use TLS to connect to the IRC server. This option is provided to support usage on overlay networks such as Tor, i2p and [yggdrassil](https://github.com/yggdrasil-network/yggdrasil-go) |
-| disableSTSFallback | Disables the "fallback" to a non-TLS connection if the strict transport policy expires and the first attempt to reconnect back to the TLS version fails |
-| allowFlood | Disable [girc](https://github.com/lrstanley/girc)'s built-in flood protection |
-| debug | Whether to enable debug logging. The logs are written to stdout |
-| out | Whether to write raw messages to stdout |
-| admins | List of admins for the bot. Only admins can use commands.<br><br>`admins = ["admin1", "admin2"]`<br> |
-| ircChannels | List of channels for the bot to join when it connects to the server.<br>`ircChannels = [["#channel1","channel1password"], ["#channel2",""], ["#channel3"]]`<br>In the provided example, milla will attempt to join `#channel1` with the provided password while for the other two channels, it will try to join normally.<br><br>**_NOTE 1_**: This behaviour is consistant across all places where a channel name is the input.<br><br>**_NOTE 2_**: Please note that the bot does not have to join a channel to be usable. One can simply query the bot directly as well.<br> |
-| databaseUser | Name of the database user |
-| databasePassword | Password for the database user |
-| databaseAddress | Address of the database |
-| databaseName | Name of the database |
-| scrapeChannels | List of channels that the bot will scrape into a database table. You can later on use these databases for the custom commands.<br><br>`ircChannels = [["#channel1","channel1password"], ["#channel2",""], ["#channel3"]]` |
-| ircProxy | Determines which proxy to use to connect to the IRC network:<br>`ircProxy = "socks5://127.0.0.1:9050"` |
-| llmProxy | Determines which proxy to use to connect to the LLM endpoint:<br>`llmProxy = "socks5://127.0.0.1:9050"` |
-| generalProxy | Determines which proxy to use for other things:<br>`llmProxy = "socks5://127.0.0.1:9050"`<br><br>**_NOTE_**: Lua scripts do not use the `generalProxy` option. They will use whatever proxy that the invidividual script has them use. The RSS functionaly lets you use a proxy for every single entry. |
-| ircdName | Name of the milla instance, must be unique across all instances |
-| adminOnly | Milla will only answer if the nick is in the admin list |
-| webIRCGateway | webirc gateway to use |
-| webIRCHostname | webirc hostname to use |
-| webIRCPassword | webirc password to use |
-| webIRCAddress | webirc address to use |
-| context | Artificially provide a history of messages for the bot.<br><br>`tomlcontext = ["you are a pirate. use the language and words a pirate would unless you are asked to do otherwise explicitly", "your name is caption blackbeard"]`<br>`tomlcontext = ["please respond in french even if i use another language unless you are specifically asked to use any language other than french", "your name is terra"]` |
-| rssFile | The file that contains the rss feeeds |
-| channel | The channel to send the rss feeds to |
-| plugins | A list of plugins to load:`plugins = ["./plugins/rss.lua", "./plugins/test.lua"]` |
-| systemPrompt | The system prompt for the AI chat bot |
-| temperature | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
-| topP | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
-| topK | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
-| ollamaMirostat | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
-| ollamaMirostatEta | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
-| ollamaMirostatTau | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
-| ollamaNumCtx | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
-| ollamaRepeatLastN | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
-| ollamaRepeatPenalty | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
-| ollamaSeed | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
-| ollamaNumPredict | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
-| ollamaMinp | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
+| Option | Description |
+| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| ircServer | The address for the IRC server to connect to |
+| ircNick | The nick the bot should use |
+| enableSasl | Whether to use SASL for authentication |
+| ircSaslUser | The SASL username |
+| ircSaslPass | The SASL password for SASL plain authentication. Can also be passed as and environment variable |
+| Endpoint | The address for the Ollama chat endpoint |
+| model | The name of the model to use |
+| chromaStyle | The style to use for syntax highlighting done by [chroma](https://github.com/alecthomas/chroma). This is basically what's called a "theme" |
+| chromaFormatter | The formatter to use. This tells chroma how to generate the color in the output. The supported options are:<br><br>- `noop` for no syntax highlighting<br>- `terminal` for 8-color terminals<br>- `terminal8` for 8-color terminals<br>- `terminal16` for 16-color terminals<br>- `terminal256` for 256-color terminals<br>- `terminal16m` for truecolor terminals<br>- `html` for HTML output<br><br>**_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. |
+| provider | Which LLM provider to use. The supported options are:<br><br>- [ollama](https://github.com/ollama/ollama)<br>- chatgpt<br>- gemini<br>- [openrouter](https://openrouter.ai/)<br> |
+| apikey | The apikey to use for the LLM provider. Can also be passed as and environment variable |
+| clientCertPath | The path to the client certificate to use for client cert authentication |
+| serverPass | The password to use for the IRC server the bot is trying to connect to if the server has a password. Can also be passed as and environment variable |
+| bind | Which address to bind to for the IRC server |
+| requestTimeout | The timeout for requests made to the LLM provider |
+| millaReconnectDelay | How much to wait before reconnecting to the IRC server |
+| ircPort | Which port to connect to for the IRC server |
+| keepAlive | |
+| memoryLimit | How many conversations to keep in memory for a model |
+| pingDelay | Ping delay for the IRC server |
+| pingTimeout | Ping timeout for the IRC server |
+| skipTLSVerify | Skip verifying the IRC server's TLS certificate. This only makes sense if you are trying to connect to an IRC server with a self-signed certificate |
+| useTLS | Whether to use TLS to connect to the IRC server. This option is provided to support usage on overlay networks such as Tor, i2p and [yggdrassil](https://github.com/yggdrasil-network/yggdrasil-go) |
+| disableSTSFallback | Disables the "fallback" to a non-TLS connection if the strict transport policy expires and the first attempt to reconnect back to the TLS version fails |
+| allowFlood | Disable [girc](https://github.com/lrstanley/girc)'s built-in flood protection |
+| debug | Whether to enable debug logging. The logs are written to stdout |
+| out | Whether to write raw messages to stdout |
+| admins | List of admins for the bot. Only admins can use commands.<br><br>`admins = ["admin1", "admin2"]`<br> |
+| ircChannels | List of channels for the bot to join when it connects to the server.<br>`ircChannels = [["#channel1","channel1password"], ["#channel2",""], ["#channel3"]]`<br>In the provided example, milla will attempt to join `#channel1` with the provided password while for the other two channels, it will try to join normally.<br><br>**_NOTE 1_**: This behaviour is consistant across all places where a channel name is the input.<br><br>**_NOTE 2_**: Please note that the bot does not have to join a channel to be usable. One can simply query the bot directly as well.<br> |
+| databaseUser | Name of the database user |
+| databasePassword | Password for the database user |
+| databaseAddress | Address of the database |
+| databaseName | Name of the database |
+| scrapeChannels | List of channels that the bot will scrape into a database table. You can later on use these databases for the custom commands.<br><br>`ircChannels = [["#channel1","channel1password"], ["#channel2",""], ["#channel3"]]` |
+| ircProxy | Determines which proxy to use to connect to the IRC network:<br>`ircProxy = "socks5://127.0.0.1:9050"` |
+| llmProxy | Determines which proxy to use to connect to the LLM endpoint:<br>`llmProxy = "socks5://127.0.0.1:9050"` |
+| generalProxy | Determines which proxy to use for other things:<br>`llmProxy = "socks5://127.0.0.1:9050"`<br><br>**_NOTE_**: Lua scripts do not use the `generalProxy` option. They will use whatever proxy that the invidividual script has them use. The RSS functionaly lets you use a proxy for every single entry. |
+| ircdName | Name of the milla instance, must be unique across all instances |
+| adminOnly | Milla will only answer if the nick is in the admin list |
+| webIRCGateway | webirc gateway to use |
+| webIRCHostname | webirc hostname to use |
+| webIRCPassword | webirc password to use |
+| webIRCAddress | webirc address to use |
+| context | Artificially provide a history of messages for the bot.<br><br>`tomlcontext = ["you are a pirate. use the language and words a pirate would unless you are asked to do otherwise explicitly", "your name is caption blackbeard"]`<br>`tomlcontext = ["please respond in french even if i use another language unless you are specifically asked to use any language other than french", "your name is terra"]` |
+| rssFile | The file that contains the rss feeeds |
+| channel | The channel to send the rss feeds to |
+| plugins | A list of plugins to load:`plugins = ["./plugins/rss.lua", "./plugins/test.lua"]` |
+| systemPrompt | The system prompt for the AI chat bot |
+| temperature | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
+| topP | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
+| topK | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
+| ollamaMirostat | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
+| ollamaMirostatEta | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
+| ollamaMirostatTau | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
+| ollamaNumCtx | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
+| ollamaRepeatLastN | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
+| ollamaRepeatPenalty | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
+| ollamaSeed | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
+| ollamaNumPredict | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
+| ollamaMinp | [ollama docs](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values) |
+| ircBackOffInitialInterval | Initial backoff value for reconnects to IRC. The value is in milliseconds. |
+| ircBackOffRandomizationFactor | The randomization factor for the exponential backoff. |
+| ircBackOffMultiplier | The multiplier for subsequent backoffs. |
+| ircBackOffMaxInterval | The maximum value for the backoff interval. The value is in seconds. |
+| dbBackOffInitialInterval | Initial backoff value for reconnects to the DB. The value is in milliseconds. |
+| dbBackOffRandomizationFactor | The randomization factor for the exponential backoff. |
+| dbBackOffMultiplier | The multiplier for subsequent backoffs. |
+| dbBackOffMaxInterval | The maximum value for the backoff interval. The value is in seconds. |
## Custom Commands
@@ -514,6 +522,13 @@ rss_feed()
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/>
Also please note that this is just an example script. If you want milla to handle some rss feeds for you, you can use the builtin rss functionality.<br/>
+### Plugins
+
+| Name | Explanation |
+| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| ip.lua | A geo-ip lookup for both ipv4 and ipv6. The API request is sent to http://ip-api.com. You can set the `http_proxy` env var for this script. |
+| urban.lua | Asks urban dictionary for the meaning. it only has one switch, `-n`, e.g. `-n 3` which will give you 3 definitions instead of the default 1 answer that will be returned. |
+
### Milla's Lua Module
Here's a list of the available function in milla's lua module:
diff --git a/defaults.go b/defaults.go
new file mode 100644
index 0000000..60217ae
--- /dev/null
+++ b/defaults.go
@@ -0,0 +1,135 @@
+package main
+
+func AddSaneDefaults(config *TomlConfig) {
+ if config.IrcNick == "" {
+ config.IrcNick = "milla"
+ }
+
+ if config.ChromaStyle == "" {
+ config.ChromaStyle = "rose-pine-moon"
+ }
+
+ if config.ChromaFormatter == "" {
+ config.ChromaFormatter = "noop"
+ }
+
+ if config.DatabaseAddress == "" {
+ config.DatabaseAddress = "postgres"
+ }
+
+ if config.DatabaseUser == "" {
+ config.DatabaseUser = "milla"
+ }
+
+ if config.DatabaseName == "" {
+ config.DatabaseName = "milladb"
+ }
+
+ if config.Temperature == 0 {
+ config.Temperature = 0.5
+ }
+
+ if config.RequestTimeout == 0 {
+ config.RequestTimeout = 10
+ }
+
+ if config.MillaReconnectDelay == 0 {
+ config.MillaReconnectDelay = 30
+ }
+
+ if config.IrcPort == 0 {
+ config.IrcPort = 6697
+ }
+
+ if config.KeepAlive == 0 {
+ config.KeepAlive = 600
+ }
+
+ if config.MemoryLimit == 0 {
+ config.MemoryLimit = 20
+ }
+
+ if config.PingDelay == 0 {
+ config.PingDelay = 20
+ }
+
+ if config.PingTimeout == 0 {
+ config.PingTimeout = 20
+ }
+
+ if config.OllamaMirostatEta == 0 {
+ config.OllamaMirostatEta = 0.1
+ }
+
+ if config.OllamaMirostatTau == 0 {
+ config.OllamaMirostatTau = 5.0
+ }
+
+ if config.OllamaNumCtx == 0 {
+ config.OllamaNumCtx = 4096
+ }
+
+ if config.OllamaRepeatLastN == 0 {
+ config.OllamaRepeatLastN = 64
+ }
+
+ if config.OllamaRepeatPenalty == 0 {
+ config.OllamaRepeatPenalty = 1.1
+ }
+
+ if config.OllamaSeed == 0 {
+ config.OllamaSeed = 42
+ }
+
+ if config.OllamaNumPredict == 0 {
+ config.OllamaNumPredict = -1
+ }
+
+ if config.TopK == 0 {
+ config.TopK = 40
+ }
+
+ if config.TopP == 0.0 {
+ config.TopP = 0.9
+ }
+
+ if config.OllamaMinP == 0 {
+ config.OllamaMinP = 0.05
+ }
+
+ if config.Temperature == 0 {
+ config.Temperature = 0.7
+ }
+
+ if config.IrcBackOffMaxInterval == 0 {
+ config.IrcBackOffMaxInterval = 500
+ }
+
+ if config.IrcBackOffRandomizationFactor == 0 {
+ config.IrcBackOffRandomizationFactor = 0.5
+ }
+
+ if config.IrcBackOffMultiplier == 0 {
+ config.IrcBackOffMultiplier = 1.5
+ }
+
+ if config.IrcBackOffMaxInterval == 0 {
+ config.IrcBackOffMaxInterval = 60
+ }
+
+ if config.DbBackOffMaxInterval == 0 {
+ config.DbBackOffMaxInterval = 500
+ }
+
+ if config.DbBackOffRandomizationFactor == 0 {
+ config.DbBackOffRandomizationFactor = 0.5
+ }
+
+ if config.DbBackOffMultiplier == 0 {
+ config.DbBackOffMultiplier = 1.5
+ }
+
+ if config.DbBackOffMaxInterval == 0 {
+ config.DbBackOffMaxInterval = 60
+ }
+}
diff --git a/go.mod b/go.mod
index a90ae32..ffb4127 100644
--- a/go.mod
+++ b/go.mod
@@ -16,7 +16,7 @@ require (
github.com/layeh/gopher-json v0.0.0-20201124131017-552bb3c4c3bf
github.com/lrstanley/girc v0.0.0-20240125042120-9add3166e52e
github.com/mmcdole/gofeed v1.3.0
- github.com/sashabaranov/go-openai v1.19.3
+ github.com/sashabaranov/go-openai v1.36.0
github.com/yuin/gluare v0.0.0-20170607022532-d7c94f1a80ed
github.com/yuin/gopher-lua v1.1.1
gitlab.com/megalithic-llc/gluasocket v0.3.1
@@ -31,8 +31,8 @@ require (
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
cloud.google.com/go/longrunning v0.5.7 // indirect
- github.com/PuerkitoBio/goquery v1.8.0 // indirect
- github.com/andybalholm/cascadia v1.3.1 // indirect
+ github.com/PuerkitoBio/goquery v1.9.2 // indirect
+ github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.1 // indirect
diff --git a/go.sum b/go.sum
index 3501135..c0cee7c 100644
--- a/go.sum
+++ b/go.sum
@@ -13,8 +13,8 @@ cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuA
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
-github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
+github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
+github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
github.com/ailncode/gluaxmlpath v0.0.0-20161126153117-6ce478ecb4a6 h1:FM0WudTZ+xeiXPJcs+X1Zg8JXe4vlb9P2GZYqCYjZkk=
github.com/ailncode/gluaxmlpath v0.0.0-20161126153117-6ce478ecb4a6/go.mod h1:Ti1AvV2KUYtHEBX7eYbdAGEfFyKz9+lHrJPcr79Vkng=
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
@@ -23,8 +23,8 @@ github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF
github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
-github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
-github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
+github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
+github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/cenkalti/backoff/v5 v5.0.1 h1:kGZdCHH1+eW+Yd0wftimjMuhg9zidDvNF5aGdnkkb+U=
github.com/cenkalti/backoff/v5 v5.0.1/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -121,8 +121,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
-github.com/sashabaranov/go-openai v1.19.3 h1:xJvkU8Tye6MOKLaoqjh7qXYwKiEYGtlmp06cb8179yo=
-github.com/sashabaranov/go-openai v1.19.3/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
+github.com/sashabaranov/go-openai v1.36.0 h1:fcSrn8uGuorzPWCBp8L0aCR95Zjb/Dd+ZSML0YZy9EI=
+github.com/sashabaranov/go-openai v1.36.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -133,8 +133,11 @@ 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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/vitoraguila/forza v0.0.0-20250308133152-28e9d4675774 h1:BCGeQcdyr+aqVU+mS2roqivUzv7d9wy9oeMze43SUe0=
+github.com/vitoraguila/forza v0.0.0-20250308133152-28e9d4675774/go.mod h1:h3MHloI9aeI+xO4S4tpQ+XEAJt370k/g7l6FZaj76Mc=
github.com/yuin/gluare v0.0.0-20170607022532-d7c94f1a80ed h1:I1vcLHWU9m30rA90rMrKPu0eD3NDA4FBlkB8WMaDyUw=
github.com/yuin/gluare v0.0.0-20170607022532-d7c94f1a80ed/go.mod h1:9w6KSdZh23UWqOywWsRLUcJUrUNjRh4Ql3z9uVgnSP4=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
gitlab.com/megalithic-llc/gluasocket v0.3.1 h1:CtsSTZa3G5WnMbhZ3TgvpLwpVlQv6KjO2mqxNOGrhY4=
@@ -153,19 +156,26 @@ go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2L
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -174,6 +184,8 @@ golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -181,13 +193,22 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
@@ -197,6 +218,10 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.186.0 h1:n2OPp+PPXX0Axh4GuSsL5QL8xQCTb2oDwyzPnQvqUug=
google.golang.org/api v0.186.0/go.mod h1:hvRbBmgoje49RV3xqVXrmP6w93n6ehGgIVPYrGtBFFc=
diff --git a/main.go b/main.go
index 265c8c9..db8acad 100644
--- a/main.go
+++ b/main.go
@@ -47,108 +47,6 @@ var (
errUnsupportedType = errors.New("unsupported type")
)
-func addSaneDefaults(config *TomlConfig) {
- if config.IrcNick == "" {
- config.IrcNick = "milla"
- }
-
- if config.ChromaStyle == "" {
- config.ChromaStyle = "rose-pine-moon"
- }
-
- if config.ChromaFormatter == "" {
- config.ChromaFormatter = "noop"
- }
-
- if config.DatabaseAddress == "" {
- config.DatabaseAddress = "postgres"
- }
-
- if config.DatabaseUser == "" {
- config.DatabaseUser = "milla"
- }
-
- if config.DatabaseName == "" {
- config.DatabaseName = "milladb"
- }
-
- if config.Temperature == 0 {
- config.Temperature = 0.5
- }
-
- if config.RequestTimeout == 0 {
- config.RequestTimeout = 10
- }
-
- if config.MillaReconnectDelay == 0 {
- config.MillaReconnectDelay = 30
- }
-
- if config.IrcPort == 0 {
- config.IrcPort = 6697
- }
-
- if config.KeepAlive == 0 {
- config.KeepAlive = 600
- }
-
- if config.MemoryLimit == 0 {
- config.MemoryLimit = 20
- }
-
- if config.PingDelay == 0 {
- config.PingDelay = 20
- }
-
- if config.PingTimeout == 0 {
- config.PingTimeout = 20
- }
-
- if config.OllamaMirostatEta == 0 {
- config.OllamaMirostatEta = 0.1
- }
-
- if config.OllamaMirostatTau == 0 {
- config.OllamaMirostatTau = 5.0
- }
-
- if config.OllamaNumCtx == 0 {
- config.OllamaNumCtx = 4096
- }
-
- if config.OllamaRepeatLastN == 0 {
- config.OllamaRepeatLastN = 64
- }
-
- if config.OllamaRepeatPenalty == 0 {
- config.OllamaRepeatPenalty = 1.1
- }
-
- if config.OllamaSeed == 0 {
- config.OllamaSeed = 42
- }
-
- if config.OllamaNumPredict == 0 {
- config.OllamaNumPredict = -1
- }
-
- if config.TopK == 0 {
- config.TopK = 40
- }
-
- if config.TopP == 0.0 {
- config.TopP = 0.9
- }
-
- if config.OllamaMinP == 0 {
- config.OllamaMinP = 0.05
- }
-
- if config.Temperature == 0 {
- config.Temperature = 0.7
- }
-}
-
func getTableFromChanName(channel, ircdName string) string {
tableName := ircdName + "_" + channel
tableName = strings.ReplaceAll(tableName, "#", "")
@@ -585,7 +483,7 @@ func runCommand(
appConfig.deleteLstate(args[1])
case "remind":
if len(args) < 2 { //nolint: mnd,gomnd
- client.Cmd.Reply(event, errNotEnoughArgs.Error())
+ client.Cmd.Message(event.Source.Name, errNotEnoughArgs.Error())
break
}
@@ -593,14 +491,15 @@ func runCommand(
seconds, err := strconv.Atoi(args[1])
if err != nil {
client.Cmd.Reply(event, errNotEnoughArgs.Error())
+ client.Cmd.Message(event.Source.Name, errNotEnoughArgs.Error())
break
}
- client.Cmd.Reply(event, "Ok, I'll remind you in "+args[1]+" seconds.")
+ client.Cmd.Message(event.Source.Name, "Ok, I'll remind you in "+args[1]+" seconds.")
time.Sleep(time.Duration(seconds) * time.Second)
- client.Cmd.ReplyTo(event, " Ping!")
+ client.Cmd.Message(event.Source.Name, "Ping!")
case "forget":
client.Cmd.Reply(event, "I no longer even know whether you're supposed to wear or drink a camel.'")
case "whois":
@@ -1221,7 +1120,14 @@ func connectToDB(appConfig *TomlConfig, ctx *context.Context, irc *girc.Client)
return pgxpool.NewWithConfig(*ctx, poolConfig)
}
- pool, err = backoff.Retry(*ctx, dbConnect, backoff.WithBackOff(backoff.NewExponentialBackOff()))
+ expBackoff := backoff.WithBackOff(&backoff.ExponentialBackOff{
+ InitialInterval: time.Millisecond * time.Duration(appConfig.DbBackOffInitialInterval),
+ RandomizationFactor: appConfig.DbBackOffRandomizationFactor,
+ Multiplier: appConfig.DbBackOffMultiplier,
+ MaxInterval: time.Second * time.Duration(appConfig.DbBackOffMaxInterval),
+ })
+
+ pool, err = backoff.Retry(*ctx, dbConnect, expBackoff)
if err != nil {
LogError(err)
}
@@ -1475,6 +1381,8 @@ func runIRC(appConfig TomlConfig) {
go LoadAllPlugins(&appConfig, irc)
+ go LoadAllEventPlugins(&appConfig, irc)
+
if appConfig.DatabaseAddress != "" {
context, cancel := context.WithTimeout(context.Background(), time.Duration(appConfig.RequestTimeout)*time.Second)
defer cancel()
@@ -1541,11 +1449,20 @@ func runIRC(appConfig TomlConfig) {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(appConfig.MillaReconnectDelay)*time.Second)
defer cancel()
- _, err := backoff.Retry(ctx, connectToIRC, backoff.WithBackOff(backoff.NewExponentialBackOff()))
- if err != nil {
- LogError(err)
- } else {
- return
+ for {
+ expBackoff := backoff.WithBackOff(&backoff.ExponentialBackOff{
+ InitialInterval: time.Millisecond * time.Duration(appConfig.IrcBackOffInitialInterval),
+ RandomizationFactor: appConfig.IrcBackOffRandomizationFactor,
+ Multiplier: appConfig.IrcBackOffMultiplier,
+ MaxInterval: time.Second * time.Duration(appConfig.IrcBackOffMaxInterval),
+ })
+
+ _, err := backoff.Retry(ctx, connectToIRC, expBackoff)
+ if err != nil {
+ LogError(err)
+ } else {
+ return
+ }
}
}
@@ -1560,6 +1477,7 @@ func main() {
signal.Notify(quitChannel, syscall.SIGINT, syscall.SIGTERM)
configPath := flag.String("config", "./config.toml", "path to the config file")
+ prof := flag.Bool("prof", false, "enable prof server")
flag.Parse()
@@ -1576,7 +1494,7 @@ func main() {
}
for key, value := range config.Ircd {
- addSaneDefaults(&value)
+ AddSaneDefaults(&value)
value.IRCDName = key
config.Ircd[key] = value
}
@@ -1589,10 +1507,12 @@ func main() {
go runIRC(v)
}
- go func() {
- err := http.ListenAndServe(":6060", nil)
- log.Println(err)
- }()
+ if *prof {
+ go func() {
+ err := http.ListenAndServe(":6060", nil)
+ log.Println(err)
+ }()
+ }
<-quitChannel
}
diff --git a/plugins.go b/plugins.go
index 744ae94..f728e6a 100644
--- a/plugins.go
+++ b/plugins.go
@@ -7,6 +7,7 @@ import (
"net/url"
"os"
"reflect"
+ "strings"
"github.com/ailncode/gluaxmlpath"
"github.com/cjoudrey/gluahttp"
@@ -197,6 +198,17 @@ func sendMessageClosure(luaState *lua.LState, client *girc.Client) func(*lua.LSt
return 0
}
}
+
+func replyToMessageClosure(luaStete *lua.LState, client *girc.Client, event girc.Event) func(*lua.LState) int {
+ return func(luaState *lua.LState) int {
+ message := luaState.CheckString(1)
+
+ client.Cmd.Message(event.Source.Name, message)
+
+ return 0
+ }
+}
+
func registerLuaCommand(luaState *lua.LState, appConfig *TomlConfig) func(*lua.LState) int {
return func(luaState *lua.LState) int {
path := luaState.CheckString(1)
@@ -410,6 +422,37 @@ func millaModuleLoaderClosure(luaState *lua.LState, client *girc.Client, appConf
}
}
+func millaModuleLoaderEventClosure(luaState *lua.LState, client *girc.Client, appConfig *TomlConfig, event girc.Event) func(*lua.LState) int {
+ return func(luaState *lua.LState) int {
+ exports := map[string]lua.LGFunction{
+ "send_message": lua.LGFunction(sendMessageClosure(luaState, client)),
+ "reply_to": lua.LGFunction(replyToMessageClosure(luaState, client, event)),
+ "join_channel": lua.LGFunction(ircJoinChannelClosure(luaState, client)),
+ "part_channel": lua.LGFunction(ircPartChannelClosure(luaState, client)),
+ "send_ollama_request": lua.LGFunction(ollamaRequestClosure(luaState, appConfig)),
+ "send_gemini_request": lua.LGFunction(geminiRequestClosure(luaState, appConfig)),
+ "send_chatgpt_request": lua.LGFunction(chatGPTRequestClosure(luaState, appConfig)),
+ "send_or_request": lua.LGFunction(orRequestClosure(luaState, appConfig)),
+ "query_db": lua.LGFunction(dbQueryClosure(luaState, appConfig)),
+ "register_cmd": lua.LGFunction(registerLuaCommand(luaState, appConfig)),
+ "url_encode": lua.LGFunction(urlEncode(luaState)),
+ }
+ millaModule := luaState.SetFuncs(luaState.NewTable(), exports)
+
+ registerStructAsLuaMetaTable[TomlConfig](luaState, millaModule, checkStruct, TomlConfig{}, "toml_config")
+ registerStructAsLuaMetaTable[CustomCommand](luaState, millaModule, checkStruct, CustomCommand{}, "custom_command")
+ registerStructAsLuaMetaTable[LogModel](luaState, millaModule, checkStruct, LogModel{}, "log_model")
+ registerStructAsLuaMetaTable[girc.Source](luaState, millaModule, checkStruct, girc.Source{}, "girc_source")
+ registerStructAsLuaMetaTable[girc.Event](luaState, millaModule, checkStruct, girc.Event{}, "girc_event")
+
+ luaState.SetGlobal("milla", millaModule)
+
+ luaState.Push(millaModule)
+
+ return 1
+ }
+}
+
func RunScript(scriptPath string, client *girc.Client, appConfig *TomlConfig) {
luaState := lua.NewState()
defer luaState.Close()
@@ -469,6 +512,15 @@ func LoadAllPlugins(appConfig *TomlConfig, client *girc.Client) {
}
}
+func LoadAllEventPlugins(appConfig *TomlConfig, client *girc.Client) {
+ for _, triggeredScript := range appConfig.TriggeredScripts {
+ log.Print("Loading event plugin: ", triggeredScript.Path)
+
+ go RunScript(triggeredScript.Path, client, appConfig)
+ registerTriggeredScripts(client, *appConfig)
+ }
+}
+
func RunLuaFunc(
cmd, args string,
client *girc.Client,
@@ -565,7 +617,7 @@ func RunTriggeredLuaFunc(
appConfig.insertLState(scriptPath, luaState, cancel)
- luaState.PreloadModule("milla", millaModuleLoaderClosure(luaState, client, appConfig))
+ luaState.PreloadModule("milla", millaModuleLoaderEventClosure(luaState, client, appConfig, event))
gluasocket.Preload(luaState)
gluaxmlpath.Preload(luaState)
luaState.PreloadModule("yaml", gluayaml.Loader)
@@ -629,10 +681,18 @@ func RunTriggeredLuaFunc(
func registerTriggeredScripts(irc *girc.Client, appConfig TomlConfig) {
for _, triggeredScript := range appConfig.TriggeredScripts {
- for _, triggerType := range triggeredScript.TriggerType {
+ for _, triggerType := range triggeredScript.TriggerTypes {
switch triggerType {
case girc.PRIVMSG:
irc.Handlers.AddBg(girc.PRIVMSG, func(_ *girc.Client, event girc.Event) {
+ if !strings.HasPrefix(event.Last(), appConfig.IrcNick+": ") {
+ return
+ }
+
+ if appConfig.AdminOnly && !isFromAdmin(appConfig.Admins, event) {
+ return
+ }
+
RunTriggeredLuaFunc(triggeredScript.FuncName, triggeredScript.Path, irc, event, &appConfig)
})
default:
diff --git a/plugins/hello.lua b/plugins/hello.lua
new file mode 100644
index 0000000..b9c5bc2
--- /dev/null
+++ b/plugins/hello.lua
@@ -0,0 +1,5 @@
+local milla = require("milla")
+
+function hello() milla.reply_to("hello") end
+
+milla.register_cmd("/plugins/hello.lua", "hello", "hello")
diff --git a/plugins/repology.lua b/plugins/repology.lua
new file mode 100644
index 0000000..f3de647
--- /dev/null
+++ b/plugins/repology.lua
@@ -0,0 +1,33 @@
+local milla = require("milla")
+local os = require("os")
+local json = require("json")
+
+-- /repology void_x86_64
+function repology(arg)
+ os.setenv("http_proxy", "http://172.17.0.1:8120")
+
+ local http = require("http")
+
+ local url = "https://repology.org/api/v1/repository/" .. arg .. "/problems"
+
+ local response = http.request("GET", url)
+
+ io.write(response.body)
+
+ local json_response, err = json.decode(response.body)
+ io.write(json_response)
+ if err ~= nil then print(err) end
+
+ for _, item in pairs(json_response) do
+ for k, v in ipairs(item) do print(k, v) end
+ end
+
+ local result = ""
+ for key, value in pairs(json_response) do
+ result = result .. key .. ": " .. value .. " -- "
+ end
+
+ return result
+end
+
+milla.register_cmd("/plugins/repology.lua", "repology", "repology")
diff --git a/rss.go b/rss.go
index 8ac96cc..a8c864d 100644
--- a/rss.go
+++ b/rss.go
@@ -65,7 +65,7 @@ func GetFeed(feed FeedConfig,
for _, item := range parsedFeed.Items {
if item.PublishedParsed.Unix() > newestFromDB {
- client.Cmd.Message(channel[0], parsedFeed.Title+": "+item.Title+" >>> "+item.Link)
+ client.Cmd.Message(channel[0], feed.Name[0:Min(20, len(feed.Name))]+": "+parsedFeed.Title+": "+item.Title+" >>> "+item.Link)
}
}
diff --git a/types.go b/types.go
index bfc0216..fd37291 100644
--- a/types.go
+++ b/types.go
@@ -19,6 +19,12 @@ type LogModel struct {
// DateAdded pgtype.Date `db:"dateadded"`
}
+type PluginType struct {
+ Name string `toml:"name"`
+ Path string `toml:"path"`
+ EnvVars [][]string `toml:"envVars"`
+}
+
type CustomCommand struct {
SQL string `toml:"sql"`
Limit int `toml:"limit"`
@@ -48,10 +54,10 @@ type LuaCommand struct {
}
type TriggeredScripts struct {
- Path string
- FuncName string
- Channels [][]string
- TriggerType []string
+ Path string
+ FuncName string
+ Channels [][]string
+ TriggerTypes []string
}
type RssFile struct {
@@ -60,74 +66,82 @@ type RssFile struct {
}
type TomlConfig struct {
- IrcServer string `toml:"ircServer"`
- IrcNick string `toml:"ircNick"`
- IrcSaslUser string `toml:"ircSaslUser"`
- IrcSaslPass string `toml:"ircSaslPass"`
- Endpoint string `toml:"endpoint"`
- Model string `toml:"model"`
- ChromaStyle string `toml:"chromaStyle"`
- ChromaFormatter string `toml:"chromaFormatter"`
- Provider string `toml:"provider"`
- Apikey string `toml:"apikey"`
- ClientCertPath string `toml:"clientCertPath"`
- ServerPass string `toml:"serverPass"`
- Bind string `toml:"bind"`
- Name string `toml:"name"`
- DatabaseAddress string `toml:"databaseAddress"`
- DatabasePassword string `toml:"databasePassword"`
- DatabaseUser string `toml:"databaseUser"`
- DatabaseName string `toml:"databaseName"`
- LLMProxy string `toml:"llmProxy"`
- IRCProxy string `toml:"ircProxy"`
- GeneralProxy string `toml:"generalProxy"`
- IRCDName string `toml:"ircdName"`
- WebIRCPassword string `toml:"webIRCPassword"`
- WebIRCGateway string `toml:"webIRCGateway"`
- WebIRCHostname string `toml:"webIRCHostname"`
- WebIRCAddress string `toml:"webIRCAddress"`
- RSSFile string `toml:"rssFile"`
- AnthropicVersion string `toml:"anthropicVersion"`
- Plugins []string `toml:"plugins"`
- Context []string `toml:"context"`
- SystemPrompt string `toml:"systemPrompt"`
- CustomCommands map[string]CustomCommand `toml:"customCommands"`
- WatchLists map[string]WatchList `toml:"watchList"`
- LuaStates map[string]LuaLstates
- LuaCommands map[string]LuaCommand
- TriggeredScripts map[string]TriggeredScripts
- Rss map[string]RssFile `toml:"rss"`
- RequestTimeout int `toml:"requestTimeout"`
- MillaReconnectDelay int `toml:"millaReconnectDelay"`
- IrcPort int `toml:"ircPort"`
- KeepAlive int `toml:"keepAlive"`
- MemoryLimit int `toml:"memoryLimit"`
- PingDelay int `toml:"pingDelay"`
- PingTimeout int `toml:"pingTimeout"`
- OllamaMirostat int `json:"ollamaMirostat"`
- OllamaMirostatEta float64 `json:"ollamaMirostatEta"`
- OllamaMirostatTau float64 `json:"ollamaMirostatTau"`
- OllamaNumCtx int `json:"ollamaNumCtx"`
- OllamaRepeatLastN int `json:"ollamaRepeatLastN"`
- OllamaRepeatPenalty float64 `json:"ollamaRepeatPenalty"`
- Temperature float64 `json:"temperature"`
- OllamaSeed int `json:"ollamaSeed"`
- OllamaNumPredict int `json:"ollamaNumPredict"`
- OllamaMinP float64 `json:"ollamaMinP"`
- TopP float32 `toml:"topP"`
- TopK int32 `toml:"topK"`
- EnableSasl bool `toml:"enableSasl"`
- SkipTLSVerify bool `toml:"skipTLSVerify"`
- UseTLS bool `toml:"useTLS"`
- DisableSTSFallback bool `toml:"disableSTSFallback"`
- AllowFlood bool `toml:"allowFlood"`
- Debug bool `toml:"debug"`
- Out bool `toml:"out"`
- AdminOnly bool `toml:"adminOnly"`
- pool *pgxpool.Pool
- Admins []string `toml:"admins"`
- IrcChannels [][]string `toml:"ircChannels"`
- ScrapeChannels [][]string `toml:"scrapeChannels"`
+ IrcServer string `toml:"ircServer"`
+ IrcNick string `toml:"ircNick"`
+ IrcSaslUser string `toml:"ircSaslUser"`
+ IrcSaslPass string `toml:"ircSaslPass"`
+ Endpoint string `toml:"endpoint"`
+ Model string `toml:"model"`
+ ChromaStyle string `toml:"chromaStyle"`
+ ChromaFormatter string `toml:"chromaFormatter"`
+ Provider string `toml:"provider"`
+ Apikey string `toml:"apikey"`
+ ClientCertPath string `toml:"clientCertPath"`
+ ServerPass string `toml:"serverPass"`
+ Bind string `toml:"bind"`
+ Name string `toml:"name"`
+ DatabaseAddress string `toml:"databaseAddress"`
+ DatabasePassword string `toml:"databasePassword"`
+ DatabaseUser string `toml:"databaseUser"`
+ DatabaseName string `toml:"databaseName"`
+ LLMProxy string `toml:"llmProxy"`
+ IRCProxy string `toml:"ircProxy"`
+ GeneralProxy string `toml:"generalProxy"`
+ IRCDName string `toml:"ircdName"`
+ WebIRCPassword string `toml:"webIRCPassword"`
+ WebIRCGateway string `toml:"webIRCGateway"`
+ WebIRCHostname string `toml:"webIRCHostname"`
+ WebIRCAddress string `toml:"webIRCAddress"`
+ RSSFile string `toml:"rssFile"`
+ AnthropicVersion string `toml:"anthropicVersion"`
+ Plugins []string `toml:"plugins"`
+ Context []string `toml:"context"`
+ SystemPrompt string `toml:"systemPrompt"`
+ CustomCommands map[string]CustomCommand `toml:"customCommands"`
+ WatchLists map[string]WatchList `toml:"watchList"`
+ LuaStates map[string]LuaLstates
+ LuaCommands map[string]LuaCommand
+ TriggeredScripts map[string]TriggeredScripts
+ Rss map[string]RssFile `toml:"rss"`
+ RequestTimeout int `toml:"requestTimeout"`
+ MillaReconnectDelay int `toml:"millaReconnectDelay"`
+ IrcPort int `toml:"ircPort"`
+ KeepAlive int `toml:"keepAlive"`
+ MemoryLimit int `toml:"memoryLimit"`
+ PingDelay int `toml:"pingDelay"`
+ PingTimeout int `toml:"pingTimeout"`
+ OllamaMirostat int `json:"ollamaMirostat"`
+ OllamaMirostatEta float64 `json:"ollamaMirostatEta"`
+ OllamaMirostatTau float64 `json:"ollamaMirostatTau"`
+ OllamaNumCtx int `json:"ollamaNumCtx"`
+ OllamaRepeatLastN int `json:"ollamaRepeatLastN"`
+ OllamaRepeatPenalty float64 `json:"ollamaRepeatPenalty"`
+ Temperature float64 `json:"temperature"`
+ OllamaSeed int `json:"ollamaSeed"`
+ OllamaNumPredict int `json:"ollamaNumPredict"`
+ OllamaMinP float64 `json:"ollamaMinP"`
+ TopP float32 `toml:"topP"`
+ TopK int32 `toml:"topK"`
+ IrcBackOffInitialInterval int `toml:"ircBackOffInitialInterval"`
+ IrcBackOffRandomizationFactor float64 `toml:"ircbackOffRandomizationFactor"`
+ IrcBackOffMultiplier float64 `toml:"ircBackOffMultiplier"`
+ IrcBackOffMaxInterval int `toml:"ircBackOffMaxInterval"`
+ DbBackOffInitialInterval int `toml:"dbBackOffInitialInterval"`
+ DbBackOffRandomizationFactor float64 `toml:"dbBackOffRandomizationFactor"`
+ DbBackOffMultiplier float64 `toml:"dbBackOffMultiplier"`
+ DbBackOffMaxInterval int `toml:"dbBackOffMaxInterval"`
+ EnableSasl bool `toml:"enableSasl"`
+ SkipTLSVerify bool `toml:"skipTLSVerify"`
+ UseTLS bool `toml:"useTLS"`
+ DisableSTSFallback bool `toml:"disableSTSFallback"`
+ AllowFlood bool `toml:"allowFlood"`
+ Debug bool `toml:"debug"`
+ Out bool `toml:"out"`
+ AdminOnly bool `toml:"adminOnly"`
+ pool *pgxpool.Pool
+ Admins []string `toml:"admins"`
+ IrcChannels [][]string `toml:"ircChannels"`
+ ScrapeChannels [][]string `toml:"scrapeChannels"`
}
func (config *TomlConfig) insertLState(
@@ -176,9 +190,9 @@ func (config *TomlConfig) insertTriggeredScript(path, cmd string, triggerType []
config.TriggeredScripts = make(map[string]TriggeredScripts)
}
config.TriggeredScripts[path] = TriggeredScripts{
- Path: path,
- FuncName: cmd,
- TriggerType: triggerType,
+ Path: path,
+ FuncName: cmd,
+ TriggerTypes: triggerType,
}
}
diff --git a/utils.go b/utils.go
index 6bf0015..d990504 100644
--- a/utils.go
+++ b/utils.go
@@ -7,6 +7,13 @@ import (
"github.com/lrstanley/girc"
)
+func Min(x, y int) int {
+ if x < y {
+ return x
+ }
+ return y
+}
+
func IrcJoin(irc *girc.Client, channel []string) {
if len(channel) > 1 && channel[1] != "" {
irc.Cmd.JoinKey(channel[0], channel[1])