diff --git a/go.mod b/go.mod index 3088761..03b6d5e 100644 --- a/go.mod +++ b/go.mod @@ -1,37 +1,60 @@ module github.com/autobrr/autobrr -go 1.16 +go 1.17 require ( github.com/Masterminds/squirrel v1.5.1 - github.com/anacrolix/dht/v2 v2.5.1 // indirect - github.com/anacrolix/missinggo v1.3.0 // indirect - github.com/anacrolix/missinggo/v2 v2.5.2 // indirect github.com/anacrolix/torrent v1.11.0 github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef + github.com/dcarbone/zadapters/zstdlog v0.3.1 github.com/dustin/go-humanize v1.0.0 + github.com/ergochat/irc-go v0.1.0 github.com/gdm85/go-libdeluge v0.5.5 github.com/go-chi/chi v1.5.4 github.com/gorilla/sessions v1.2.1 - github.com/kr/pretty v0.3.0 // indirect github.com/lib/pq v1.10.4 github.com/mattn/go-sqlite3 v1.14.10 github.com/pkg/errors v0.9.1 github.com/r3labs/sse/v2 v2.7.2 - github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/rs/cors v1.8.0 - github.com/rs/zerolog v1.26.0 + github.com/rs/zerolog v1.26.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.9.0 github.com/stretchr/testify v1.7.0 - golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 - golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e + golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac - gopkg.in/ini.v1 v1.64.0 // indirect - gopkg.in/irc.v3 v3.1.4 gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.4.0 ) + +require ( + github.com/anacrolix/dht/v2 v2.5.1 // indirect + github.com/anacrolix/missinggo v1.3.0 // indirect + github.com/anacrolix/missinggo/v2 v2.5.2 // indirect + github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/gdm85/go-rencode v0.1.8 // indirect + github.com/gorilla/securecookie v1.1.1 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/magiconair/properties v1.8.5 // indirect + github.com/mitchellh/mapstructure v1.4.2 // indirect + github.com/pelletier/go-toml v1.9.4 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.8.0 // indirect + github.com/spf13/afero v1.6.0 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/text v0.3.7 // indirect + gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect + gopkg.in/ini.v1 v1.64.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/go.sum b/go.sum index 2288238..187dd1a 100644 --- a/go.sum +++ b/go.sum @@ -153,11 +153,14 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dcarbone/zadapters/zstdlog v0.3.1 h1:BB2brDbBxH+/Dec96EI3Mb9/LC0aFcnDJm9bVqiJRv0= +github.com/dcarbone/zadapters/zstdlog v0.3.1/go.mod h1:/eRABVjBs/8NYkN8LLRkBuQJVc6PFVxdec3WbvgIzoc= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= @@ -175,6 +178,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ergochat/irc-go v0.1.0 h1:jBHUayERH9SiPOWe4ePDWRztBjIQsU/jwLbbGUuiOWM= +github.com/ergochat/irc-go v0.1.0/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= @@ -440,9 +445,11 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so= github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE= -github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo= +github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo= +github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= +github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= @@ -521,8 +528,8 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= -golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e h1:1SzTfNOXwIS2oWiMF+6qu0OUDKb0dauo6MoDUQyu+yU= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -604,9 +611,8 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 h1:0qxwC5n+ttVOINCBeRHO0nq9X7uy8SDsPoi5OaCdIEI= -golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -701,8 +707,9 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -736,6 +743,7 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -923,8 +931,6 @@ gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWd gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.64.0 h1:Mj2zXEXcNb5joEiSA0zc3HZpTst/iyjNiR4CN8tDzOg= gopkg.in/ini.v1 v1.64.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/irc.v3 v3.1.4 h1:DYGMRFbtseXEh+NadmMUFzMraqyuUj4I3iWYFEzDZPc= -gopkg.in/irc.v3 v3.1.4/go.mod h1:shO2gz8+PVeS+4E6GAny88Z0YVVQSxQghdrMVGQsR9s= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/internal/domain/irc.go b/internal/domain/irc.go index 06bbf09..3ee164e 100644 --- a/internal/domain/irc.go +++ b/internal/domain/irc.go @@ -35,23 +35,18 @@ type IrcNetwork struct { } type IrcNetworkWithHealth struct { - ID int64 `json:"id"` - Name string `json:"name"` - Enabled bool `json:"enabled"` - Server string `json:"server"` - Port int `json:"port"` - TLS bool `json:"tls"` - Pass string `json:"pass"` - InviteCommand string `json:"invite_command"` - NickServ NickServ `json:"nickserv,omitempty"` - //Channels []IrcChannel `json:"channels"` - Channels []ChannelWithHealth `json:"channels"` - //Channels []struct { - // IrcChannel - // ChannelHealth - //} `json:"channels"` - Connected bool `json:"connected"` - ConnectedSince time.Time `json:"connected_since"` + ID int64 `json:"id"` + Name string `json:"name"` + Enabled bool `json:"enabled"` + Server string `json:"server"` + Port int `json:"port"` + TLS bool `json:"tls"` + Pass string `json:"pass"` + InviteCommand string `json:"invite_command"` + NickServ NickServ `json:"nickserv,omitempty"` + Channels []ChannelWithHealth `json:"channels"` + Connected bool `json:"connected"` + ConnectedSince time.Time `json:"connected_since"` } type ChannelWithHealth struct { diff --git a/internal/irc/handler.go b/internal/irc/handler.go index 0f7c4e3..0c19298 100644 --- a/internal/irc/handler.go +++ b/internal/irc/handler.go @@ -1,22 +1,22 @@ package irc import ( - "context" "crypto/tls" - "errors" "fmt" - "net" "regexp" "strings" + "sync" "time" "github.com/autobrr/autobrr/internal/announce" "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/filter" + "github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/release" + "github.com/ergochat/irc-go/ircevent" + "github.com/ergochat/irc-go/ircmsg" "github.com/rs/zerolog/log" - "gopkg.in/irc.v3" ) var ( @@ -24,6 +24,8 @@ var ( ) type channelHealth struct { + m sync.RWMutex + name string monitoring bool monitoringSince time.Time @@ -32,19 +34,25 @@ type channelHealth struct { // SetLastAnnounce set last announce to now func (h *channelHealth) SetLastAnnounce() { + h.m.Lock() h.lastAnnounce = time.Now() + h.m.Unlock() } // SetMonitoring set monitoring and time func (h *channelHealth) SetMonitoring() { + h.m.Lock() h.monitoring = true h.monitoringSince = time.Now() + h.m.Unlock() } // resetMonitoring remove monitoring and time func (h *channelHealth) resetMonitoring() { + h.m.Lock() h.monitoring = false h.monitoringSince = time.Time{} + h.m.Unlock() } type Handler struct { @@ -54,11 +62,8 @@ type Handler struct { announceProcessors map[string]announce.Processor definitions map[string]*domain.IndexerDefinition - client *irc.Client - conn net.Conn - ctx context.Context - stopped chan struct{} - cancel context.CancelFunc + client *ircevent.Connection + m sync.RWMutex lastPing time.Time connected bool @@ -73,9 +78,6 @@ type Handler struct { func NewHandler(network domain.IrcNetwork, filterService filter.Service, releaseService release.Service, definitions []domain.IndexerDefinition) *Handler { h := &Handler{ client: nil, - conn: nil, - ctx: nil, - stopped: make(chan struct{}), network: &network, filterService: filterService, releaseService: releaseService, @@ -102,7 +104,7 @@ func (h *Handler) InitIndexers(definitions []domain.IndexerDefinition) { h.definitions[definition.Identifier] = &definition - // indexers can use multiple channels, but it'h not common, but let'h handle that anyway. + // indexers can use multiple channels, but it's not common, but let's handle that anyway. for _, channel := range definition.IRC.Channels { // some channels are defined in mixed case channel = strings.ToLower(channel) @@ -133,177 +135,66 @@ func (h *Handler) removeIndexer() { } func (h *Handler) Run() error { - //log.Debug().Msgf("server %+v", h.network) + addr := fmt.Sprintf("%v:%d", h.network.Server, h.network.Port) - if h.network.Server == "" { - return errors.New("addr not set") + h.client = &ircevent.Connection{ + Nick: h.network.NickServ.Account, + User: h.network.NickServ.Account, + RealName: h.network.NickServ.Account, + Password: h.network.Pass, + Server: addr, + KeepAlive: 4 * time.Minute, + Timeout: 1 * time.Minute, + ReconnectFreq: 15 * time.Second, + Version: "autobrr", + QuitMessage: "bye from autobrr", + Debug: true, + Log: logger.StdLeveledLogger, } - ctx, cancel := context.WithCancel(context.Background()) - h.ctx = ctx - h.cancel = cancel - - dialer := net.Dialer{ - Timeout: connectTimeout, - } - - var netConn net.Conn - var err error - - addr := fmt.Sprintf("%v:%v", h.network.Server, h.network.Port) - - // decide to use SSL or not if h.network.TLS { - tlsConf := &tls.Config{ - InsecureSkipVerify: true, - } - - netConn, err = dialer.DialContext(h.ctx, "tcp", addr) - if err != nil { - log.Error().Err(err).Msgf("failed to dial %v", addr) - return fmt.Errorf("failed to dial %q: %v", addr, err) - } - - netConn = tls.Client(netConn, tlsConf) - h.conn = netConn - } else { - netConn, err = dialer.DialContext(h.ctx, "tcp", addr) - if err != nil { - log.Error().Err(err).Msgf("failed to dial %v", addr) - return fmt.Errorf("failed to dial %q: %v", addr, err) - } - - h.conn = netConn + h.client.UseTLS = true + h.client.TLSConfig = &tls.Config{InsecureSkipVerify: true} } - log.Info().Msgf("Connected to: %v", addr) + h.client.AddConnectCallback(h.onConnect) + h.client.AddCallback("MODE", h.handleMode) + h.client.AddCallback("INVITE", h.handleInvite) + h.client.AddCallback("366", h.handleJoined) + h.client.AddCallback("PART", h.handlePart) + h.client.AddCallback("PRIVMSG", h.onMessage) - config := irc.ClientConfig{ - Nick: h.network.NickServ.Account, - User: h.network.NickServ.Account, - Name: h.network.NickServ.Account, - Pass: h.network.Pass, - Handler: irc.HandlerFunc(h.handleMessage), - } - - // Create the client - client := irc.NewClient(h.conn, config) - - h.client = client - - // set connected since now - h.setConnectionStatus() - - // Connect - err = client.RunContext(ctx) - if err != nil { - log.Error().Err(err).Msgf("could not connect to %v", addr) + if err := h.client.Connect(); err != nil { + log.Error().Stack().Err(err).Msgf("%v: connect error", h.network.Server) // reset connection status on handler and channels h.resetConnectionStatus() - return err + //return err } + // set connected since now + h.setConnectionStatus() + + h.client.Loop() + return nil } -func (h *Handler) handleMessage(c *irc.Client, m *irc.Message) { - switch m.Command { - case "001": - // 001 is a welcome event, so we join channels there - err := h.onConnect(h.network.Channels) - if err != nil { - log.Error().Msgf("error joining channels %v", err) - } - - case "372", "375", "376": - // Handle MOTD - - // 322 TOPIC - // 333 UP - // 353 @ - // 396 Displayed host - case "366": // JOINED - h.handleJoined(m) - - case "JOIN": - if h.isOurNick(m.Prefix.Name) { - log.Trace().Msgf("%v: JOIN %v", h.network.Server, m) - } - - case "QUIT": - if h.isOurNick(m.Prefix.Name) { - log.Trace().Msgf("%v: QUIT %v", h.network.Server, m) - } - - case "433": - // TODO: handle nick in use - log.Debug().Msgf("%v: NICK IN USE: %v", h.network.Server, m) - - case "448", "473", "475", "477": - // TODO: handle join failed - log.Debug().Msgf("%v: JOIN FAILED %v: %v", h.network.Server, m.Params[1], m) - - case "900": // Invite bot logged in - log.Debug().Msgf("%v: %v", h.network.Server, m.Trailing()) - - case "KICK": - log.Debug().Msgf("%v: KICK: %v", h.network.Server, m) - - case "MODE": - err := h.handleMode(m) - if err != nil { - log.Error().Err(err).Msgf("error MODE change: %v", m) - } - - case "INVITE": - // TODO: handle invite - log.Debug().Msgf("%v: INVITE: %v", h.network.Server, m) - - case "PART": - // TODO: handle parted - if h.isOurNick(m.Prefix.Name) { - log.Debug().Msgf("%v: PART: %v", h.network.Server, m) - } - - case "PRIVMSG": - err := h.onMessage(m) - if err != nil { - log.Error().Msgf("error on message %v", err) - } - - case "CAP": - log.Debug().Msgf("%v: CAP: %v", h.network.Server, m) - - case "NOTICE": - log.Trace().Msgf("%v: %v", h.network.Server, m) - - case "PING": - err := h.handlePing(m) - if err != nil { - log.Error().Stack().Err(err) - } - - //case "372": - // log.Debug().Msgf("372: %v", m) - //default: - // log.Trace().Msgf("%v: %v", h.network.Server, m) - } - return -} - func (h *Handler) isOurNick(nick string) bool { return h.network.NickServ.Account == nick } func (h *Handler) setConnectionStatus() { + h.m.Lock() // set connected since now h.connectedSince = time.Now() h.connected = true + h.m.Unlock() } func (h *Handler) resetConnectionStatus() { + h.m.Lock() // set connected false if we loose connection or stop h.connectedSince = time.Time{} h.connected = false @@ -314,6 +205,8 @@ func (h *Handler) resetConnectionStatus() { h.resetMonitoring() } } + + h.m.Unlock() } func (h *Handler) GetNetwork() *domain.IrcNetwork { @@ -321,121 +214,101 @@ func (h *Handler) GetNetwork() *domain.IrcNetwork { } func (h *Handler) UpdateNetwork(network *domain.IrcNetwork) { + h.m.Lock() h.network = network + h.m.Unlock() } func (h *Handler) SetNetwork(network *domain.IrcNetwork) { + h.m.Lock() h.network = network + h.m.Unlock() } func (h *Handler) Stop() { - h.cancel() - - if !h.isStopped() { - close(h.stopped) - } - - if h.conn != nil { - h.conn.Close() - } -} - -func (h *Handler) isStopped() bool { - select { - case <-h.stopped: - return true - default: - return false - } + log.Debug().Msgf("%v: Disconnecting...", h.network.Server) + h.client.Quit() } func (h *Handler) Restart() error { - h.cancel() + log.Debug().Msgf("%v: Restarting network...", h.network.Server) - if !h.isStopped() { - close(h.stopped) - } + h.client.Quit() - if h.conn != nil { - h.conn.Close() - } - - time.Sleep(2 * time.Second) + time.Sleep(4 * time.Second) return h.Run() } -func (h *Handler) onConnect(channels []domain.IrcChannel) error { +func (h *Handler) onConnect(m ircmsg.Message) { identified := false - time.Sleep(2 * time.Second) + time.Sleep(4 * time.Second) if h.network.NickServ.Password != "" { - err := h.handleNickServPRIVMSG(h.network.NickServ.Account, h.network.NickServ.Password) + err := h.HandleNickServIdentify(h.network.NickServ.Account, h.network.NickServ.Password) if err != nil { log.Error().Stack().Err(err).Msgf("error nickserv: %v", h.network.Name) - return err + return } identified = true } - time.Sleep(3 * time.Second) + time.Sleep(4 * time.Second) if h.network.InviteCommand != "" { err := h.handleConnectCommands(h.network.InviteCommand) if err != nil { log.Error().Stack().Err(err).Msgf("error sending connect command %v to network: %v", h.network.InviteCommand, h.network.Name) - return err + return } - time.Sleep(2 * time.Second) + time.Sleep(1 * time.Second) } if !identified { - for _, channel := range channels { + for _, channel := range h.network.Channels { err := h.HandleJoinChannel(channel.Name, channel.Password) if err != nil { - log.Error().Stack().Err(err) - return err + log.Error().Stack().Err(err).Msgf("error joining channels %v", err) + return } } } - return nil } -func (h *Handler) OnJoin(msg string) (interface{}, error) { - return nil, nil -} - -func (h *Handler) onMessage(msg *irc.Message) error { +func (h *Handler) onMessage(msg ircmsg.Message) { + if len(msg.Params) < 2 { + return + } // parse announce - channel := &msg.Params[0] - announcer := &msg.Name - message := msg.Trailing() + announcer := msg.Nick() + channel := msg.Params[0] + message := msg.Params[1] // check if message is from a valid channel, if not return - validChannel := h.isValidChannel(*channel) + validChannel := h.isValidChannel(channel) if !validChannel { - return nil + return } // check if message is from announce bot, if not return - validAnnouncer := h.isValidAnnouncer(*announcer) + validAnnouncer := h.isValidAnnouncer(announcer) if !validAnnouncer { - return nil + return } // clean message cleanedMsg := cleanMessage(message) - log.Debug().Msgf("%v: %v %v: %v", h.network.Server, *channel, *announcer, cleanedMsg) + log.Debug().Msgf("%v: %v %v: %v", h.network.Server, channel, announcer, cleanedMsg) - if err := h.sendToAnnounceProcessor(*channel, cleanedMsg); err != nil { + if err := h.sendToAnnounceProcessor(channel, cleanedMsg); err != nil { log.Error().Stack().Err(err).Msgf("could not queue line: %v", cleanedMsg) - return err + return } - return nil + return } func (h *Handler) sendToAnnounceProcessor(channel string, msg string) error { @@ -464,57 +337,64 @@ func (h *Handler) sendToAnnounceProcessor(channel string, msg string) error { return nil } -func (h *Handler) sendPrivMessage(msg string) error { - msg = strings.TrimLeft(msg, "/") - privMsg := fmt.Sprintf("PRIVMSG %h", msg) +func (h *Handler) HandleJoinChannel(channel string, password string) error { + // support channel password + ch := channel + if password != "" { + ch = fmt.Sprintf("%v %v", channel, password) + } - err := h.client.Write(privMsg) + log.Trace().Msgf("%v: JOIN sending %v", h.network.Server, ch) + + time.Sleep(1 * time.Second) + + //err := h.client.Write(m.String()) + err := h.client.Join(ch) if err != nil { - log.Error().Err(err).Msgf("could not send priv msg: %v", msg) + log.Error().Stack().Err(err).Msgf("error handling join: %v", channel) return err } return nil } -func (h *Handler) HandleJoinChannel(channel string, password string) error { - // support channel password - params := []string{channel} - if password != "" { - params = append(params, password) +func (h *Handler) handlePart(msg ircmsg.Message) { + if !h.isOurNick(msg.Nick()) { + log.Debug().Msgf("%v: MODE OTHER USER: %+v", h.network.Server, msg) + return } - m := irc.Message{ - Command: "JOIN", - Params: params, - } + channel := msg.Params[0] - log.Trace().Msgf("%v: sending %v", h.network.Server, m.String()) + log.Debug().Msgf("%v: PART channel %v", h.network.Server, channel) - time.Sleep(1 * time.Second) - - err := h.client.Write(m.String()) + err := h.client.Part(channel) if err != nil { - log.Error().Stack().Err(err).Msgf("error handling join: %v", m.String()) - return err + log.Error().Err(err).Msgf("error handling part: %v", channel) + return } - return nil + // reset monitoring status + v, ok := h.channelHealth[channel] + if !ok { + return + } + + v.resetMonitoring() + + // TODO remove announceProcessor + + log.Info().Msgf("%v: Left channel '%v'", h.network.Server, channel) + + return } func (h *Handler) HandlePartChannel(channel string) error { - m := irc.Message{ - Command: "PART", - Params: []string{channel}, - } + log.Debug().Msgf("%v: PART channel %v", h.network.Server, channel) - log.Debug().Msgf("%v: %v", h.network.Server, m.String()) - - time.Sleep(1 * time.Second) - - err := h.client.Write(m.String()) + err := h.client.Part(channel) if err != nil { - log.Error().Err(err).Msgf("error handling part: %v", m.String()) + log.Error().Err(err).Msgf("error handling part: %v", channel) return err } @@ -533,19 +413,33 @@ func (h *Handler) HandlePartChannel(channel string) error { return nil } -func (h *Handler) handleJoined(msg *irc.Message) { - log.Debug().Msgf("%v: JOINED: %v", h.network.Server, msg.Params[1]) +func (h *Handler) handleJoined(msg ircmsg.Message) { + if !h.isOurNick(msg.Params[0]) { + log.Debug().Msgf("%v: OTHER USER JOINED: %+v", h.network.Server, msg) + return + } // get channel - channel := &msg.Params[1] + channel := msg.Params[1] + + valid := h.isValidChannel(channel) + if !valid { + if err := h.HandlePartChannel(channel); err != nil { + log.Error().Stack().Err(err).Msgf("%v: Could not part channel: %v", h.network.Server, channel) + return + } + return + } + + log.Debug().Msgf("%v: JOINED: %v", h.network.Server, msg.Params[1]) // set monitoring on current channelHealth, or add new - v, ok := h.channelHealth[strings.ToLower(*channel)] + v, ok := h.channelHealth[strings.ToLower(channel)] if ok { v.SetMonitoring() } else if v == nil { - h.channelHealth[*channel] = &channelHealth{ - name: *channel, + h.channelHealth[channel] = &channelHealth{ + name: channel, monitoring: true, monitoringSince: time.Now(), } @@ -561,16 +455,16 @@ func (h *Handler) handleConnectCommands(msg string) error { for _, command := range connectCommands { cmd := strings.TrimSpace(command) - m := irc.Message{ + m := ircmsg.Message{ Command: "PRIVMSG", Params: strings.Split(cmd, " "), } log.Debug().Msgf("%v: sending connect command", h.network.Server) - err := h.client.Write(m.String()) + err := h.client.SendIRCMessage(m) if err != nil { - log.Error().Err(err).Msgf("error handling invite: %v", m.String()) + log.Error().Err(err).Msgf("error handling invite: %v", m) return err } } @@ -578,52 +472,41 @@ func (h *Handler) handleConnectCommands(msg string) error { return nil } -func (h *Handler) handlePRIVMSG(msg string) error { - msg = strings.TrimLeft(msg, "/") - - m := irc.Message{ - Command: "PRIVMSG", - Params: []string{msg}, +func (h *Handler) handleInvite(msg ircmsg.Message) { + if len(msg.Params) < 2 { + return } - log.Debug().Msgf("%v: Handle privmsg: %v", h.network.Server, m.String()) - err := h.client.Write(m.String()) + // get channel + channel := msg.Params[1] + + valid := h.isValidChannel(channel) + if !valid { + return + } + + log.Debug().Msgf("%v: INVITE from %v, joining %v", h.network.Server, msg.Nick(), channel) + + err := h.client.Join(channel) if err != nil { - log.Error().Err(err).Msgf("error handling PRIVMSG: %v", m.String()) - return err + log.Error().Stack().Err(err).Msgf("error handling join: %v", channel) + return } - return nil -} - -func (h *Handler) handleNickServPRIVMSG(nick, password string) error { - m := irc.Message{ - Command: "PRIVMSG", - Params: []string{"NickServ", "IDENTIFY", nick, password}, - } - - log.Debug().Msgf("%v: NickServ: %v", h.network.Server, m.String()) - - err := h.client.Write(m.String()) - if err != nil { - log.Error().Err(err).Msgf("error identifying with nickserv: %v", m.String()) - return err - } - - return nil + return } func (h *Handler) HandleNickServIdentify(nick, password string) error { - m := irc.Message{ + m := ircmsg.Message{ Command: "PRIVMSG", Params: []string{"NickServ", "IDENTIFY", nick, password}, } - log.Debug().Msgf("%v: NickServ: %v", h.network.Server, m.String()) + log.Debug().Msgf("%v: NickServ: %v", h.network.Server, m) - err := h.client.Write(m.String()) + err := h.client.SendIRCMessage(m) if err != nil { - log.Error().Stack().Err(err).Msgf("error identifying with nickserv: %v", m.String()) + log.Error().Stack().Err(err).Msgf("error identifying with nickserv: %v", m) return err } @@ -631,30 +514,26 @@ func (h *Handler) HandleNickServIdentify(nick, password string) error { } func (h *Handler) HandleNickChange(nick string) error { - m := irc.Message{ - Command: "NICK", - Params: []string{nick}, - } + log.Debug().Msgf("%v: Nick change: %v", h.network.Server, nick) - log.Debug().Msgf("%v: Nick change: %v", h.network.Server, m.String()) - - err := h.client.Write(m.String()) - if err != nil { - log.Error().Stack().Err(err).Msgf("error changing nick: %v", m.String()) - return err - } + h.client.SetNick(nick) return nil } -func (h *Handler) handleMode(msg *irc.Message) error { - log.Debug().Msgf("%v: MODE: %v %v", h.network.Server, msg.User, msg.Trailing()) +func (h *Handler) handleMode(msg ircmsg.Message) { + log.Debug().Msgf("%v: MODE: %+v", h.network.Server, msg) + + if !h.isOurNick(msg.Params[0]) { + log.Debug().Msgf("%v: MODE OTHER USER: %+v", h.network.Server, msg) + return + } time.Sleep(2 * time.Second) - if h.network.NickServ.Password != "" && !strings.Contains(msg.String(), h.client.CurrentNick()) || !strings.Contains(msg.String(), "+r") { - log.Trace().Msgf("%v: MODE: Not correct permission yet: %v", h.network.Server, msg.String()) - return nil + if h.network.NickServ.Password != "" && !strings.Contains(msg.Params[0], h.client.Nick) || !strings.Contains(msg.Params[1], "+r") { + log.Trace().Msgf("%v: MODE: Not correct permission yet: %v", h.network.Server, msg.Params) + return } for _, ch := range h.network.Channels { @@ -667,28 +546,7 @@ func (h *Handler) handleMode(msg *irc.Message) error { time.Sleep(1 * time.Second) } - return nil -} - -func (h *Handler) handlePing(msg *irc.Message) error { - log.Trace().Msgf("%v: %v", h.network.Server, msg) - - pong := irc.Message{ - Command: "PONG", - Params: msg.Params, - } - - log.Trace().Msgf("%v: %v", h.network.Server, pong.String()) - - err := h.client.Write(pong.String()) - if err != nil { - log.Error().Err(err).Msgf("error PING PONG response: %v", pong.String()) - return err - } - - h.setLastPing() - - return nil + return } // check if announcer is one from the list in the definition diff --git a/internal/irc/service.go b/internal/irc/service.go index 20ebcfb..18381b0 100644 --- a/internal/irc/service.go +++ b/internal/irc/service.go @@ -114,7 +114,7 @@ func (s *service) startNetwork(network domain.IrcNetwork) error { if existingHandler, found := s.handlers[handlerKey{network.Server, network.NickServ.Account}]; found { log.Debug().Msgf("starting network: %+v", network.Name) - if existingHandler.conn != nil { + if !existingHandler.client.Connected() { go func() { if err := existingHandler.Run(); err != nil { log.Error().Err(err).Msgf("failed to start existingHandler for network %q", existingHandler.network.Name) @@ -164,7 +164,7 @@ func (s *service) checkIfNetworkRestartNeeded(network *domain.IrcNetwork) error // if server, tls, invite command, port : changed - restart // if nickserv account, nickserv password : changed - stay connected, and change those // if channels len : changes - join or leave - if existingHandler.conn != nil { + if existingHandler.client.Connected() { handler := existingHandler.GetNetwork() restartNeeded := false @@ -289,9 +289,9 @@ func (s *service) checkIfNetworkRestartNeeded(network *domain.IrcNetwork) error func (s *service) restartNetwork(network domain.IrcNetwork) error { // look if we have the network in handlers, if so restart it if existingHandler, found := s.handlers[handlerKey{network.Server, network.NickServ.Account}]; found { - log.Info().Msgf("restarting network: %+v", network.Name) + log.Info().Msgf("restarting network: %v", network.Name) - if existingHandler.conn != nil { + if existingHandler.client.Connected() { go func() { if err := existingHandler.Restart(); err != nil { log.Error().Err(err).Msgf("failed to restart network %q", existingHandler.network.Name) @@ -401,9 +401,11 @@ func (s *service) GetNetworksWithHealth(ctx context.Context) ([]domain.IrcNetwor handler, ok := s.handlers[handlerKey{n.Server, n.NickServ.Account}] if ok { // only set connected and connected since if we have an active handler and connection - if handler.conn != nil { + if handler.client.Connected() { + handler.m.RLock() netw.Connected = handler.connected netw.ConnectedSince = handler.connectedSince + handler.m.RUnlock() } } @@ -432,9 +434,12 @@ func (s *service) GetNetworksWithHealth(ctx context.Context) ([]domain.IrcNetwor chan1, ok := handler.channelHealth[name] if ok { + chan1.m.RLock() ch.Monitoring = chan1.monitoring ch.MonitoringSince = chan1.monitoringSince ch.LastAnnounce = chan1.lastAnnounce + + chan1.m.RUnlock() } } diff --git a/internal/logger/logger.go b/internal/logger/logger.go index e4ddd8d..538825f 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -2,11 +2,13 @@ package logger import ( "io" + stdlog "log" "os" "time" "github.com/autobrr/autobrr/internal/domain" + "github.com/dcarbone/zadapters/zstdlog" "github.com/r3labs/sse/v2" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -14,7 +16,13 @@ import ( "gopkg.in/natefinch/lumberjack.v2" ) +var ( + StdLogger *stdlog.Logger + StdLeveledLogger *stdlog.Logger +) + func Setup(cfg domain.Config, sse *sse.Server) { + zerolog.TimeFieldFormat = time.RFC3339 zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack @@ -52,4 +60,13 @@ func Setup(cfg domain.Config, sse *sse.Server) { log.Logger = log.Hook(&ServerSentEventHook{sse: sse}) log.Logger = log.Output(writers) + + // init a logger to use + //log := zerolog.New(os.Stdout) + + // creates a *log.Logger with no level prefix + StdLogger = zstdlog.NewStdLogger(log.Logger) + + // creates a *log.Logger with a level prefix + StdLeveledLogger = zstdlog.NewStdLoggerWithLevel(log.Logger, zerolog.TraceLevel) }