diff options
author | terminaldweller <devi@terminaldweller.com> | 2024-05-15 14:41:39 +0000 |
---|---|---|
committer | terminaldweller <devi@terminaldweller.com> | 2024-05-15 14:41:39 +0000 |
commit | 006cb3a77d7437a60733c28abee0083e6f02ae90 (patch) | |
tree | 2cae44510125ce8eb09d4ee4e142396c7769536f | |
parent | fixing the go executable build for github actions (diff) | |
download | milla-006cb3a77d7437a60733c28abee0083e6f02ae90.tar.gz milla-006cb3a77d7437a60733c28abee0083e6f02ae90.zip |
fixes #14, fixes #16, fixes #17, fixes #18, fixes #19, fixes #20
-rw-r--r-- | .github/workflows/docker.yaml | 1 | ||||
-rw-r--r-- | .golangci.yml | 1 | ||||
-rw-r--r-- | README.md | 185 | ||||
-rw-r--r-- | docker-compose-postgres.yaml | 102 | ||||
-rw-r--r-- | go.mod | 4 | ||||
-rw-r--r-- | go.sum | 10 | ||||
-rw-r--r-- | main.go | 216 |
7 files changed, 498 insertions, 21 deletions
diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 71b2988..33354ce 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -35,6 +35,7 @@ jobs: context: . file: ./Dockerfile push: true + sbom: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} provenance: mode=max diff --git a/.golangci.yml b/.golangci.yml index 8a79a07..42f9a39 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -17,3 +17,4 @@ linters-settings: - github.com/lrstanley/girc - github.com/sashabaranov/go-openai - github.com/BurntSushi/toml + - github.com/jackc/pgx/v5/pgxpool @@ -1,6 +1,6 @@ # milla -Milla is an IRC bot that sends things over to an LLM when you ask it questions and prints the answer with optional syntax-hilighting.<br/> +Milla is an IRC bot that sends things over to an LLM when you ask it questions and prints the answer with optional syntax-highlighting.<br/> Currently Supported: - Ollama @@ -39,7 +39,7 @@ The SASL username. #### ircSaslPass -The SASL password for SASL plain authentication. +The SASL password for SASL plain authentication. Can also be passed as and environment variable. #### ollamaEndpoint @@ -77,7 +77,7 @@ Which LLM provider to use. The supported options are: #### apikey -The apikey to use for the LLM provider. +The apikey to use for the LLM provider. Can also be passed as and environment variable. #### ollamaSystem @@ -89,7 +89,7 @@ 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. +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 @@ -169,6 +169,38 @@ List of channels for the bot to join when it connects to the server. ircChannels = ["#channel1", "#channel2"] ``` +### databaseUser + +Name of the database user. Can also be passed an an environment variable. + +### databasePassword + +Password for the database user. Can also be passed an an environment variable. + +### databaseAddress + +Address of the database. Can also be passed as and environment variable. + +### databaseName + +Name of the database. Can also be passed as and environment variable. + +### ircProxy + +Determines which proxy to use to connect to the irc network: + +``` +ircProxy = "socks5://127.0.0.1:9050" +``` + +### llmProxy + +Determines which proxy to use to connect to the LLM endpoint: + +``` +llmProxy = "socks5://127.0.0.1:9050" +``` + ## Commands #### help @@ -187,6 +219,20 @@ Get the value of all config options. Set a config option on the fly. Use the same name as the config file but capitalized. +#### memstats + +Returns memory stats for milla. + +## Environment Variables + +- MILLA_SASL_PASSWORD +- MILLA_SERVER_PASSWORD +- MILLA_APIKEY +- MILLA_DB_USER +- MILLA_DB_PASSWORD +- MILLA_DB_ADDRESS +- MILLA_DB_NAME + ## Proxy Support milla will read and use the `ALL_PROXY` environment variable. @@ -200,6 +246,9 @@ ALL_PROXY=127.0.0.1:9050 ## Deploy +### Docker + +Images are automatically pushed to dockerhub. So you can get it from [there](https://hub.docker.com/r/terminaldweller/milla). An example docker compose file is provided in the repo under `docker-compose.yaml`. milla can be used with [gvisor](https://gvisor.dev/)'s docker runtime, `runsc`. @@ -234,15 +283,141 @@ networks: driver: bridge ``` +### Public Message Storage + +milla can be configured to store all incoming public messages for future use in a postgres database. An example docker compose file is provided under `docker-compose-postgres.yaml`.<br/> + +```yaml +services: + terra: + image: milla_distroless_vendored + build: + context: . + dockerfile: ./Dockerfile_distroless_vendored + deploy: + resources: + limits: + memory: 128M + logging: + driver: "json-file" + options: + max-size: "100m" + networks: + - terranet + user: 1000:1000 + restart: unless-stopped + entrypoint: ["/usr/bin/milla"] + command: ["--config", "/config.toml"] + volumes: + - ./config-gpt.toml:/config.toml + - /etc/localtime:/etc/localtime:ro + cap_drop: + - ALL + environment: + - HTTPS_PROXY=http://172.17.0.1:8120 + - https_proxy=http://172.17.0.1:8120 + - HTTP_PROXY=http://172.17.0.1:8120 + - http_proxy=http://172.17.0.1:8120 + postgres: + image: postgres:16-alpine3.19 + deploy: + resources: + limits: + memory: 4096M + logging: + driver: "json-file" + options: + max-size: "200m" + restart: unless-stopped + ports: + - "127.0.0.1:5455:5432/tcp" + volumes: + - terra_postgres_vault:/var/lib/postgresql/data + - ./scripts/:/docker-entrypoint-initdb.d/:ro + environment: + - POSTGRES_PASSWORD_FILE=/run/secrets/pg_pass_secret + - POSTGRES_USER_FILE=/run/secrets/pg_user_secret + - POSTGRES_INITDB_ARGS_FILE=/run/secrets/pg_initdb_args_secret + - POSTGRES_DB_FILE=/run/secrets/pg_db_secret + networks: + - terranet + - dbnet + secrets: + - pg_pass_secret + - pg_user_secret + - pg_initdb_args_secret + - pg_db_secret + runtime: runsc + pgadmin: + image: dpage/pgadmin4:8.6 + deploy: + resources: + limits: + memory: 1024M + logging: + driver: "json-file" + options: + max-size: "100m" + environment: + - PGADMIN_LISTEN_PORT=${PGADMIN_LISTEN_PORT:-5050} + - PGADMIN_DEFAULT_EMAIL=${PGADMIN_DEFAULT_EMAIL:-devi@terminaldweller.com} + - PGADMIN_DEFAULT_PASSWORD_FILE=/run/secrets/pgadmin_pass + - PGADMIN_DISABLE_POSTFIX=${PGADMIN_DISABLE_POSTFIX:-YES} + ports: + - "127.0.0.1:5050:5050/tcp" + restart: unless-stopped + volumes: + - terra_pgadmin_vault:/var/lib/pgadmin + networks: + - dbnet + secrets: + - pgadmin_pass +networks: + terranet: + driver: bridge + dbnet: +volumes: + terra_postgres_vault: + terra_pgadmin_vault: +secrets: + pg_pass_secret: + file: ./pg/pg_pass_secret + pg_user_secret: + file: ./pg/pg_user_secret + pg_initdb_args_secret: + file: ./pg/pg_initdb_args_secret + pg_db_secret: + file: ./pg/pg_db_secret + pgadmin_pass: + file: ./pgadmin/pgadmin_pass +``` + The env vars `UID`and `GID`need to be defined or they can replaces by your host user's uid and gid.<br/> -As a convinience, there is a a [distroless](https://github.com/GoogleContainerTools/distroless) dockerfile, `Dockerfile_distroless` also provided.<br/> +As a convenience, there is a a [distroless](https://github.com/GoogleContainerTools/distroless) dockerfile, `Dockerfile_distroless` also provided.<br/> A vendored build of milla is available by first running `go mod vendor` and then using the provided Dockerfile, `Dockerfile_distroless_vendored`.<br/> +### Build + +For a regular build: + +```sh +go mod download +go build +``` + +For a vendored build: + +```sh +go mod vendor +go build +``` + ## Thanks - [girc](https://github.com/lrstanley/girc) - [chroma](https://github.com/alecthomas/chroma) +- [pgx](https://github.com/jackc/pgx) - [ollama](https://github.com/ollama/ollama) ## Similar Projects diff --git a/docker-compose-postgres.yaml b/docker-compose-postgres.yaml new file mode 100644 index 0000000..a10e79f --- /dev/null +++ b/docker-compose-postgres.yaml @@ -0,0 +1,102 @@ +services: + terra: + image: milla_distroless_vendored + build: + context: . + dockerfile: ./Dockerfile_distroless_vendored + deploy: + resources: + limits: + memory: 128M + logging: + driver: "json-file" + options: + max-size: "100m" + networks: + - terranet + user: 1000:1000 + restart: unless-stopped + entrypoint: ["/usr/bin/milla"] + command: ["--config", "/config.toml"] + volumes: + - ./config-gpt.toml:/config.toml + - /etc/localtime:/etc/localtime:ro + cap_drop: + - ALL + environment: + - HTTPS_PROXY=http://172.17.0.1:8120 + - https_proxy=http://172.17.0.1:8120 + - HTTP_PROXY=http://172.17.0.1:8120 + - http_proxy=http://172.17.0.1:8120 + postgres: + image: postgres:16-alpine3.19 + deploy: + resources: + limits: + memory: 4096M + logging: + driver: "json-file" + options: + max-size: "200m" + restart: unless-stopped + ports: + - "127.0.0.1:5455:5432/tcp" + volumes: + - terra_postgres_vault:/var/lib/postgresql/data + - ./scripts/:/docker-entrypoint-initdb.d/:ro + environment: + - POSTGRES_PASSWORD_FILE=/run/secrets/pg_pass_secret + - POSTGRES_USER_FILE=/run/secrets/pg_user_secret + - POSTGRES_INITDB_ARGS_FILE=/run/secrets/pg_initdb_args_secret + - POSTGRES_DB_FILE=/run/secrets/pg_db_secret + networks: + - terranet + - dbnet + secrets: + - pg_pass_secret + - pg_user_secret + - pg_initdb_args_secret + - pg_db_secret + runtime: runsc + pgadmin: + image: dpage/pgadmin4:8.6 + deploy: + resources: + limits: + memory: 1024M + logging: + driver: "json-file" + options: + max-size: "100m" + environment: + - PGADMIN_LISTEN_PORT=${PGADMIN_LISTEN_PORT:-5050} + - PGADMIN_DEFAULT_EMAIL=${PGADMIN_DEFAULT_EMAIL:-devi@terminaldweller.com} + - PGADMIN_DEFAULT_PASSWORD_FILE=/run/secrets/pgadmin_pass + - PGADMIN_DISABLE_POSTFIX=${PGADMIN_DISABLE_POSTFIX:-YES} + ports: + - "127.0.0.1:5050:5050/tcp" + restart: unless-stopped + volumes: + - terra_pgadmin_vault:/var/lib/pgadmin + networks: + - dbnet + secrets: + - pgadmin_pass +networks: + terranet: + driver: bridge + dbnet: +volumes: + terra_postgres_vault: + terra_pgadmin_vault: +secrets: + pg_pass_secret: + file: ./pg/pg_pass_secret + pg_user_secret: + file: ./pg/pg_user_secret + pg_initdb_args_secret: + file: ./pg/pg_initdb_args_secret + pg_db_secret: + file: ./pg/pg_db_secret + pgadmin_pass: + file: ./pgadmin/pgadmin_pass @@ -6,6 +6,7 @@ require ( github.com/BurntSushi/toml v0.3.1 github.com/alecthomas/chroma/v2 v2.12.0 github.com/google/generative-ai-go v0.11.2 + github.com/jackc/pgx/v5 v5.5.5 github.com/lrstanley/girc v0.0.0-20240125042120-9add3166e52e github.com/sashabaranov/go-openai v1.19.3 golang.org/x/net v0.24.0 @@ -29,6 +30,9 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.4 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect @@ -75,6 +75,14 @@ github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/lrstanley/girc v0.0.0-20240125042120-9add3166e52e h1:Y86mAFtJjS4P0atZ6QAKH88TV0ASQYJdIGWiOmJKoNY= github.com/lrstanley/girc v0.0.0-20240125042120-9add3166e52e/go.mod h1:lgrnhcF8bg/Bd5HA5DOb4Z+uGqUqGnp4skr+J2GwVgI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -85,6 +93,8 @@ github.com/sashabaranov/go-openai v1.19.3/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adO 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= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -9,10 +9,13 @@ import ( "flag" "fmt" "log" + "net" "net/http" + "net/url" "os" "reflect" "regexp" + "runtime" "strconv" "strings" "time" @@ -20,6 +23,7 @@ import ( "github.com/BurntSushi/toml" "github.com/alecthomas/chroma/v2/quick" "github.com/google/generative-ai-go/genai" + "github.com/jackc/pgx/v5/pgxpool" "github.com/lrstanley/girc" openai "github.com/sashabaranov/go-openai" "golang.org/x/net/proxy" @@ -33,6 +37,7 @@ var ( errCantSet = errors.New("can't set field") errWrongDataForField = errors.New("wrong data type for field") errUnsupportedType = errors.New("unsupported type") + dbConnection *pgxpool.Pool //nolint:gochecknoglobals ) type TomlConfig struct { @@ -50,6 +55,13 @@ type TomlConfig struct { 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"` Temp float64 `toml:"temp"` RequestTimeout int `toml:"requestTimeout"` MillaReconnectDelay int `toml:"millaReconnectDelay"` @@ -69,6 +81,7 @@ type TomlConfig struct { Out bool `toml:"out"` Admins []string `toml:"admins"` IrcChannels []string `toml:"ircChannels"` + ScrapeChannels []string `toml:"scrapeChannels"` } func NewTomlConfig() *TomlConfig { @@ -78,6 +91,9 @@ func NewTomlConfig() *TomlConfig { ChromaStyle: "rose-pine-moon", ChromaFormatter: "noop", Provider: "ollama", + DatabaseAddress: "postgres", + DatabaseUser: "milla", + DatabaseName: "milladb", Temp: 0.5, //nolint:gomnd RequestTimeout: 10, //nolint:gomnd MillaReconnectDelay: 30, //nolint:gomnd @@ -206,6 +222,7 @@ func getHelpString() string { helpString += "set - set a configuration value\n" helpString += "get - get a configuration value\n" helpString += "getall - returns all config options with their value\n" + helpString += "memstats - returns the memory status currently being used\n" return helpString } @@ -251,6 +268,11 @@ func setFieldByName(v reflect.Value, field string, value string) error { return nil } +func byteToMByte(bytes uint64, +) uint64 { + return bytes / 1024 / 1024 +} + func runCommand( client *girc.Client, event girc.Event, @@ -317,6 +339,13 @@ func runCommand( fieldValue := v.Field(i).Interface() client.Cmd.Reply(event, fmt.Sprintf("%s: %v", field.Name, fieldValue)) } + case "memstats": + var memStats runtime.MemStats + runtime.ReadMemStats(&memStats) + + client.Cmd.Reply(event, fmt.Sprintf("Alloc: %d MiB", byteToMByte(memStats.Alloc))) + client.Cmd.Reply(event, fmt.Sprintf("TotalAlloc: %d MiB", byteToMByte(memStats.TotalAlloc))) + client.Cmd.Reply(event, fmt.Sprintf("Sys: %d MiB", byteToMByte(memStats.Sys))) default: client.Cmd.Reply(event, errUnknCmd.Error()) } @@ -385,12 +414,28 @@ func ollamaHandler( var httpClient http.Client - dialer := proxy.FromEnvironment() + var dialer proxy.Dialer - httpClient = http.Client{ - Transport: &http.Transport{ - Dial: dialer.Dial, - }, + if appConfig.LLMProxy != "" { + proxyURL, err := url.Parse(appConfig.IRCProxy) + if err != nil { + cancel() + + log.Fatal(err.Error()) + } + + dialer, err = proxy.FromURL(proxyURL, &net.Dialer{Timeout: time.Duration(appConfig.RequestTimeout) * time.Second}) + if err != nil { + cancel() + + log.Fatal(err.Error()) + } + + httpClient = http.Client{ + Transport: &http.Transport{ + Dial: dialer.Dial, + }, + } } response, err := httpClient.Do(request) @@ -474,6 +519,10 @@ func geminiHandler( // clientGemini, err := genai.NewClient(ctx, option.WithAPIKey(appConfig.Apikey), option.WithHTTPClient(&httpClient)) + if appConfig.Apikey == "" { + appConfig.Apikey = os.Getenv("MILLA_APIKEY") + } + clientGemini, err := genai.NewClient(ctx, option.WithAPIKey(appConfig.Apikey)) if err != nil { client.Cmd.ReplyTo(event, fmt.Sprintf("error: %s", err.Error())) @@ -559,12 +608,30 @@ func chatGPTHandler( var httpClient http.Client - dialer := proxy.FromEnvironment() + if appConfig.LLMProxy != "" { + proxyURL, err := url.Parse(appConfig.IRCProxy) + if err != nil { + cancel() - httpClient = http.Client{ - Transport: &http.Transport{ - Dial: dialer.Dial, - }, + log.Fatal(err.Error()) + } + + dialer, err := proxy.FromURL(proxyURL, &net.Dialer{Timeout: time.Duration(appConfig.RequestTimeout) * time.Second}) + if err != nil { + cancel() + + log.Fatal(err.Error()) + } + + httpClient = http.Client{ + Transport: &http.Transport{ + Dial: dialer.Dial, + }, + } + } + + if appConfig.Apikey == "" { + appConfig.Apikey = os.Getenv("MILLA_APIKEY") } config := openai.DefaultConfig(appConfig.Apikey) @@ -613,7 +680,78 @@ func chatGPTHandler( }) } -func runIRC(appConfig TomlConfig, ircChan chan *girc.Client) { +func connectToDB(appConfig TomlConfig, context *context.Context) { + for { + if appConfig.DatabaseUser == "" { + appConfig.DatabaseUser = os.Getenv("MILLA_DB_USER") + } + + if appConfig.DatabasePassword == "" { + appConfig.DatabasePassword = os.Getenv("MILLA_DB_PASSWORD") + } + + if appConfig.DatabaseAddress == "" { + appConfig.DatabaseAddress = os.Getenv("MILLA_DB_ADDRESS") + } + + if appConfig.DatabaseName == "" { + appConfig.DatabaseName = os.Getenv("MILLA_DB_NAME") + } + + dbURL := fmt.Sprintf( + "postgres://%s:%s@%s/%s", + appConfig.DatabaseUser, + appConfig.DatabasePassword, + appConfig.DatabaseAddress, + appConfig.DatabaseName) + + conn, err := pgxpool.New(*context, dbURL) + if err != nil { + log.Println(err) + time.Sleep(time.Duration(appConfig.MillaReconnectDelay) * time.Second) + } else { + for _, channel := range appConfig.ScrapeChannels { + query := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (id SERIAL PRIMARY KEY,channel TEXT NOT NULL,log TEXT NOT NULL,nick TEXT NOT NULL,dateadded TIMESTAMP DEFAULT CURRENT_TIMESTAMP)", + strings.ReplaceAll(channel, "#", "")) + + log.Println(query) + + _, err = conn.Query(*context, query) + if err != nil { + log.Println(err.Error()) + time.Sleep(time.Duration(appConfig.MillaReconnectDelay) * time.Second) + } + } + + dbConnection = conn + } + } +} + +func scrapeChannel(irc *girc.Client) { + irc.Handlers.AddBg(girc.PRIVMSG, func(client *girc.Client, event girc.Event) { + if dbConnection == nil { + log.Println("missed logging message because currently not connected to db") + + return + } + query := fmt.Sprintf("INSERT INTO %s (channel,log,nick) VALUES ('%s','%s','%s')", + strings.ReplaceAll(event.Params[0], "#", ""), + event.Params[0], + event.Last(), + event.Source.Name, + ) + log.Println(query) + + _, err := dbConnection.Query( + context.Background(), query) + if err != nil { + log.Println(err.Error()) + } + }) +} + +func runIRC(appConfig TomlConfig, ircChan chan *girc.Client, dbChan chan *pgxpool.Pool) { var OllamaMemory []MemoryElement var GeminiMemory []*genai.Content @@ -646,16 +784,29 @@ func runIRC(appConfig TomlConfig, ircChan chan *girc.Client) { irc.Config.Out = os.Stdout } - if appConfig.ServerPass != "" { - irc.Config.ServerPass = appConfig.ServerPass + if appConfig.ServerPass == "" { + appConfig.ServerPass = os.Getenv("MILLA_SERVER_PASSWORD") } + irc.Config.ServerPass = appConfig.ServerPass + if appConfig.Bind != "" { irc.Config.Bind = appConfig.Bind } + if appConfig.Name != "" { + irc.Config.Name = appConfig.Name + } + saslUser := appConfig.IrcSaslUser - saslPass := appConfig.IrcSaslPass + + var saslPass string + + if appConfig.IrcSaslPass == "" { + saslPass = os.Getenv("MILLA_SASL_PASSWORD") + } else { + saslPass = appConfig.IrcSaslPass + } if appConfig.EnableSasl && saslUser != "" && saslPass != "" { irc.Config.SASL = &girc.SASLPlain{ @@ -690,10 +841,42 @@ func runIRC(appConfig TomlConfig, ircChan chan *girc.Client) { chatGPTHandler(irc, &appConfig, &GPTMemory) } + context, cancel := context.WithTimeout(context.Background(), time.Duration(appConfig.RequestTimeout)*time.Second) + defer cancel() + + go connectToDB(appConfig, &context) + + if len(appConfig.ScrapeChannels) > 0 { + irc.Handlers.AddBg(girc.CONNECTED, func(c *girc.Client, e girc.Event) { + for _, channel := range appConfig.ScrapeChannels { + c.Cmd.Join(channel) + } + }) + + go scrapeChannel(irc) + } ircChan <- irc for { - if err := irc.Connect(); err != nil { + var dialer proxy.Dialer + + if appConfig.IRCProxy != "" { + proxyURL, err := url.Parse(appConfig.IRCProxy) + if err != nil { + cancel() + + log.Fatal(err.Error()) + } + + dialer, err = proxy.FromURL(proxyURL, &net.Dialer{Timeout: time.Duration(appConfig.RequestTimeout) * time.Second}) + if err != nil { + cancel() + + log.Fatal(err.Error()) + } + } + + if err := irc.DialerConnect(dialer); err != nil { log.Println(err) log.Println("reconnecting in " + strconv.Itoa(appConfig.MillaReconnectDelay)) time.Sleep(time.Duration(appConfig.MillaReconnectDelay) * time.Second) @@ -723,6 +906,7 @@ func main() { log.Println(appConfig) ircChan := make(chan *girc.Client, 1) + dbConn := make(chan *pgxpool.Pool, 1) - runIRC(*appConfig, ircChan) + runIRC(*appConfig, ircChan, dbConn) } |