diff --git a/cmd/autobrr/main.go b/cmd/autobrr/main.go index b148c37..b48851d 100644 --- a/cmd/autobrr/main.go +++ b/cmd/autobrr/main.go @@ -94,22 +94,25 @@ func main() { srv.Port = cfg.Port sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) + signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM) if err := srv.Start(); err != nil { - log.Fatal().Err(err).Msg("could not start server") + log.Fatal().Stack().Err(err).Msg("could not start server") + return } for sig := range sigCh { switch sig { case syscall.SIGHUP: - log.Print("shutting down server") + log.Print("shutting down server sighup") + srv.Shutdown() os.Exit(1) - case syscall.SIGINT, syscall.SIGTERM: - log.Print("shutting down server") - //srv.Shutdown() + case syscall.SIGINT, syscall.SIGQUIT: + srv.Shutdown() + os.Exit(1) + case syscall.SIGKILL, syscall.SIGTERM: + srv.Shutdown() os.Exit(1) - return } } } diff --git a/config.toml b/config.toml index b6f2057..21d101e 100644 --- a/config.toml +++ b/config.toml @@ -33,7 +33,7 @@ port = 8989 # # Options: "ERROR", "DEBUG", "INFO", "WARN" # -logLevel = "DEBUG" +logLevel = "TRACE" # Session secret # diff --git a/internal/database/indexer.go b/internal/database/indexer.go index 303c91a..3133233 100644 --- a/internal/database/indexer.go +++ b/internal/database/indexer.go @@ -26,12 +26,15 @@ func (r *IndexerRepo) Store(indexer domain.Indexer) (*domain.Indexer, error) { return nil, err } - _, err = r.db.Exec(`INSERT INTO indexer (enabled, name, identifier, settings) VALUES (?, ?, ?, ?)`, indexer.Enabled, indexer.Name, indexer.Identifier, settings) + res, err := r.db.Exec(`INSERT INTO indexer (enabled, name, identifier, settings) VALUES (?, ?, ?, ?)`, indexer.Enabled, indexer.Name, indexer.Identifier, settings) if err != nil { log.Error().Stack().Err(err).Msg("error executing query") return nil, err } + id, _ := res.LastInsertId() + indexer.ID = id + return &indexer, nil } diff --git a/internal/database/irc.go b/internal/database/irc.go index fc3c003..41f462c 100644 --- a/internal/database/irc.go +++ b/internal/database/irc.go @@ -3,7 +3,6 @@ package database import ( "context" "database/sql" - "strings" "github.com/autobrr/autobrr/internal/domain" @@ -24,7 +23,7 @@ func (ir *IrcRepo) Store(announce domain.Announce) error { func (ir *IrcRepo) GetNetworkByID(id int64) (*domain.IrcNetwork, error) { - row := ir.db.QueryRow("SELECT id, enabled, name, addr, tls, nick, pass, connect_commands, sasl_mechanism, sasl_plain_username, sasl_plain_password FROM irc_network WHERE id = ?", id) + row := ir.db.QueryRow("SELECT id, enabled, name, server, port, tls, pass, invite_command, nickserv_account, nickserv_password FROM irc_network WHERE id = ?", id) if err := row.Err(); err != nil { log.Fatal().Err(err) return nil, err @@ -32,22 +31,19 @@ func (ir *IrcRepo) GetNetworkByID(id int64) (*domain.IrcNetwork, error) { var n domain.IrcNetwork - var pass, connectCommands sql.NullString - var saslMechanism, saslPlainUsername, saslPlainPassword sql.NullString + var pass, inviteCmd sql.NullString + var nsAccount, nsPassword sql.NullString var tls sql.NullBool - if err := row.Scan(&n.ID, &n.Enabled, &n.Name, &n.Addr, &tls, &n.Nick, &pass, &connectCommands, &saslMechanism, &saslPlainUsername, &saslPlainPassword); err != nil { + if err := row.Scan(&n.ID, &n.Enabled, &n.Name, &n.Server, &n.Port, &tls, &pass, &inviteCmd, &nsAccount, &nsPassword); err != nil { log.Fatal().Err(err) } n.TLS = tls.Bool n.Pass = pass.String - if connectCommands.Valid { - n.ConnectCommands = strings.Split(connectCommands.String, "\r\n") - } - n.SASL.Mechanism = saslMechanism.String - n.SASL.Plain.Username = saslPlainUsername.String - n.SASL.Plain.Password = saslPlainPassword.String + n.InviteCommand = inviteCmd.String + n.NickServ.Account = nsAccount.String + n.NickServ.Password = nsPassword.String return &n, nil } @@ -84,7 +80,7 @@ func (ir *IrcRepo) DeleteNetwork(ctx context.Context, id int64) error { func (ir *IrcRepo) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) { - rows, err := ir.db.QueryContext(ctx, "SELECT id, enabled, name, addr, tls, nick, pass, connect_commands FROM irc_network") + rows, err := ir.db.QueryContext(ctx, "SELECT id, enabled, name, server, port, tls, pass, invite_command, nickserv_account, nickserv_password FROM irc_network") if err != nil { log.Fatal().Err(err) } @@ -95,20 +91,16 @@ func (ir *IrcRepo) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error for rows.Next() { var net domain.IrcNetwork - //var username, realname, pass, connectCommands sql.NullString - var pass, connectCommands sql.NullString + var pass, inviteCmd sql.NullString var tls sql.NullBool - if err := rows.Scan(&net.ID, &net.Enabled, &net.Name, &net.Addr, &tls, &net.Nick, &pass, &connectCommands); err != nil { + if err := rows.Scan(&net.ID, &net.Enabled, &net.Name, &net.Server, &net.Port, &tls, &pass, &inviteCmd, &net.NickServ.Account, &net.NickServ.Password); err != nil { log.Fatal().Err(err) } net.TLS = tls.Bool net.Pass = pass.String - - if connectCommands.Valid { - net.ConnectCommands = strings.Split(connectCommands.String, "\r\n") - } + net.InviteCommand = inviteCmd.String networks = append(networks, net) } @@ -131,7 +123,6 @@ func (ir *IrcRepo) ListChannels(networkID int64) ([]domain.IrcChannel, error) { for rows.Next() { var ch domain.IrcChannel - //if err := rows.Scan(&ch.ID, &ch.Name, &ch.Enabled, &ch.Pass, &ch.InviteCommand, &ch.InviteHTTPURL, &ch.InviteHTTPHeader, &ch.InviteHTTPData); err != nil { if err := rows.Scan(&ch.ID, &ch.Name, &ch.Enabled); err != nil { log.Fatal().Err(err) } @@ -149,20 +140,10 @@ func (ir *IrcRepo) StoreNetwork(network *domain.IrcNetwork) error { netName := toNullString(network.Name) pass := toNullString(network.Pass) - connectCommands := toNullString(strings.Join(network.ConnectCommands, "\r\n")) + inviteCmd := toNullString(network.InviteCommand) - var saslMechanism, saslPlainUsername, saslPlainPassword sql.NullString - if network.SASL.Mechanism != "" { - saslMechanism = toNullString(network.SASL.Mechanism) - switch network.SASL.Mechanism { - case "PLAIN": - saslPlainUsername = toNullString(network.SASL.Plain.Username) - saslPlainPassword = toNullString(network.SASL.Plain.Password) - default: - log.Warn().Msgf("unsupported SASL mechanism: %q", network.SASL.Mechanism) - //return fmt.Errorf("cannot store network: unsupported SASL mechanism %q", network.SASL.Mechanism) - } - } + nsAccount := toNullString(network.NickServ.Account) + nsPassword := toNullString(network.NickServ.Password) var err error if network.ID != 0 { @@ -170,26 +151,24 @@ func (ir *IrcRepo) StoreNetwork(network *domain.IrcNetwork) error { _, err = ir.db.Exec(`UPDATE irc_network SET enabled = ?, name = ?, - addr = ?, + server = ?, + port = ?, tls = ?, - nick = ?, pass = ?, - connect_commands = ?, - sasl_mechanism = ?, - sasl_plain_username = ?, - sasl_plain_password = ?, + invite_command = ?, + nickserv_account = ?, + nickserv_password = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, network.Enabled, netName, - network.Addr, + network.Server, + network.Port, network.TLS, - network.Nick, pass, - connectCommands, - saslMechanism, - saslPlainUsername, - saslPlainPassword, + inviteCmd, + nsAccount, + nsPassword, network.ID, ) } else { @@ -198,25 +177,23 @@ func (ir *IrcRepo) StoreNetwork(network *domain.IrcNetwork) error { res, err = ir.db.Exec(`INSERT INTO irc_network ( enabled, name, - addr, + server, + port, tls, - nick, pass, - connect_commands, - sasl_mechanism, - sasl_plain_username, - sasl_plain_password - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + invite_command, + nickserv_account, + nickserv_password + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, network.Enabled, netName, - network.Addr, + network.Server, + network.Port, network.TLS, - network.Nick, pass, - connectCommands, - saslMechanism, - saslPlainUsername, - saslPlainPassword, + inviteCmd, + nsAccount, + nsPassword, ) if err != nil { log.Error().Stack().Err(err).Msg("error executing query") diff --git a/internal/database/migrate.go b/internal/database/migrate.go index c0f30ef..a0e2209 100644 --- a/internal/database/migrate.go +++ b/internal/database/migrate.go @@ -24,7 +24,8 @@ CREATE TABLE indexer name TEXT NOT NULL, settings TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE (identifier) ); CREATE TABLE irc_network @@ -32,29 +33,30 @@ CREATE TABLE irc_network id INTEGER PRIMARY KEY, enabled BOOLEAN, name TEXT NOT NULL, - addr TEXT NOT NULL, - nick TEXT NOT NULL, + server TEXT NOT NULL, + port INTEGER NOT NULL, tls BOOLEAN, pass TEXT, - connect_commands TEXT, - sasl_mechanism TEXT, - sasl_plain_username TEXT, - sasl_plain_password TEXT, + invite_command TEXT, + nickserv_account TEXT, + nickserv_password TEXT, + connected BOOLEAN, + connected_since TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - unique (addr, nick) + UNIQUE (server, port, nickserv_account) ); CREATE TABLE irc_channel ( - id INTEGER PRIMARY KEY, - enabled BOOLEAN, - name TEXT NOT NULL, - password TEXT, - detached BOOLEAN, + id INTEGER PRIMARY KEY, + enabled BOOLEAN, + name TEXT NOT NULL, + password TEXT, + detached BOOLEAN, network_id INTEGER NOT NULL, FOREIGN KEY (network_id) REFERENCES irc_network(id), - unique (network_id, name) + UNIQUE (network_id, name) ); CREATE TABLE filter diff --git a/internal/domain/indexer.go b/internal/domain/indexer.go index 29c1afc..170f9e7 100644 --- a/internal/domain/indexer.go +++ b/internal/domain/indexer.go @@ -9,7 +9,7 @@ type IndexerRepo interface { } type Indexer struct { - ID int `json:"id"` + ID int64 `json:"id"` Name string `json:"name"` Identifier string `json:"identifier"` Enabled bool `json:"enabled"` @@ -39,15 +39,21 @@ type IndexerSetting struct { Type string `json:"type"` Value string `json:"value,omitempty"` Label string `json:"label"` - Description string `json:"description"` + Default string `json:"default,omitempty"` + Description string `json:"description,omitempty"` + Help string `json:"help,omitempty"` Regex string `json:"regex,omitempty"` } type IndexerIRC struct { - Network string - Server string - Channels []string - Announcers []string + Network string `json:"network"` + Server string `json:"server"` + Port int `json:"port"` + TLS bool `json:"tls"` + Channels []string `json:"channels"` + Announcers []string `json:"announcers"` + SettingsMap map[string]string `json:"-"` + Settings []IndexerSetting `json:"settings"` } type IndexerParse struct { diff --git a/internal/domain/irc.go b/internal/domain/irc.go index 270c5fd..464cadb 100644 --- a/internal/domain/irc.go +++ b/internal/domain/irc.go @@ -1,35 +1,37 @@ package domain -import "context" +import ( + "context" + "time" +) type IrcChannel struct { - ID int64 `json:"id"` - Enabled bool `json:"enabled"` - Detached bool `json:"detached"` - Name string `json:"name"` - Password string `json:"password"` + ID int64 `json:"id"` + Enabled bool `json:"enabled"` + Name string `json:"name"` + Password string `json:"password"` + Detached bool `json:"detached"` + Monitoring bool `json:"monitoring"` } -type SASL struct { - Mechanism string `json:"mechanism,omitempty"` - - Plain struct { - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - } `json:"plain,omitempty"` +type NickServ struct { + Account string `json:"account,omitempty"` + Password string `json:"password,omitempty"` } type IrcNetwork struct { - ID int64 `json:"id"` - Name string `json:"name"` - Enabled bool `json:"enabled"` - Addr string `json:"addr"` - TLS bool `json:"tls"` - Nick string `json:"nick"` - Pass string `json:"pass"` - ConnectCommands []string `json:"connect_commands"` - SASL SASL `json:"sasl,omitempty"` - Channels []IrcChannel `json:"channels"` + 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"` + Connected bool `json:"connected"` + ConnectedSince *time.Time `json:"connected_since"` } type IrcRepo interface { diff --git a/internal/filter/service.go b/internal/filter/service.go index b372ed0..4279800 100644 --- a/internal/filter/service.go +++ b/internal/filter/service.go @@ -183,7 +183,7 @@ func (s *service) Update(filter domain.Filter) (*domain.Filter, error) { } for _, i := range filter.Indexers { - if err = s.repo.StoreIndexerConnection(f.ID, i.ID); err != nil { + if err = s.repo.StoreIndexerConnection(f.ID, int(i.ID)); err != nil { log.Error().Err(err).Msgf("could not store filter indexer connections: %v", filter.Name) return nil, err } diff --git a/internal/http/action.go b/internal/http/action.go index ae1a04b..13d407e 100644 --- a/internal/http/action.go +++ b/internal/http/action.go @@ -2,6 +2,7 @@ package http import ( "encoding/json" + "errors" "net/http" "strconv" @@ -17,97 +18,96 @@ type actionService interface { } type actionHandler struct { - encoder encoder - actionService actionService + encoder encoder + service actionService +} + +func newActionHandler(encoder encoder, service actionService) *actionHandler { + return &actionHandler{ + encoder: encoder, + service: service, + } } func (h actionHandler) Routes(r chi.Router) { r.Get("/", h.getActions) r.Post("/", h.storeAction) - r.Delete("/{actionID}", h.deleteAction) - r.Put("/{actionID}", h.updateAction) - r.Patch("/{actionID}/toggleEnabled", h.toggleActionEnabled) + r.Delete("/{id}", h.deleteAction) + r.Put("/{id}", h.updateAction) + r.Patch("/{id}/toggleEnabled", h.toggleActionEnabled) } func (h actionHandler) getActions(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - actions, err := h.actionService.Fetch() + actions, err := h.service.Fetch() if err != nil { // encode error } - h.encoder.StatusResponse(ctx, w, actions, http.StatusOK) + h.encoder.StatusResponse(r.Context(), w, actions, http.StatusOK) } func (h actionHandler) storeAction(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - data domain.Action - ) + var data domain.Action if err := json.NewDecoder(r.Body).Decode(&data); err != nil { // encode error return } - action, err := h.actionService.Store(data) + action, err := h.service.Store(data) if err != nil { // encode error } - h.encoder.StatusResponse(ctx, w, action, http.StatusCreated) + h.encoder.StatusResponse(r.Context(), w, action, http.StatusCreated) } func (h actionHandler) updateAction(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - data domain.Action - ) + var data domain.Action if err := json.NewDecoder(r.Body).Decode(&data); err != nil { // encode error return } - action, err := h.actionService.Store(data) + action, err := h.service.Store(data) if err != nil { // encode error } - h.encoder.StatusResponse(ctx, w, action, http.StatusCreated) + h.encoder.StatusResponse(r.Context(), w, action, http.StatusCreated) } func (h actionHandler) deleteAction(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - actionID = chi.URLParam(r, "actionID") - ) + actionID, err := parseInt(chi.URLParam(r, "id")) + if err != nil { + h.encoder.StatusResponse(r.Context(), w, errors.New("bad param id"), http.StatusBadRequest) + } - // if !actionID return error - - id, _ := strconv.Atoi(actionID) - - if err := h.actionService.Delete(id); err != nil { + if err := h.service.Delete(actionID); err != nil { // encode error } - h.encoder.StatusResponse(ctx, w, nil, http.StatusNoContent) + h.encoder.StatusResponse(r.Context(), w, nil, http.StatusNoContent) } func (h actionHandler) toggleActionEnabled(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - actionID = chi.URLParam(r, "actionID") - ) + actionID, err := parseInt(chi.URLParam(r, "id")) + if err != nil { + h.encoder.StatusResponse(r.Context(), w, errors.New("bad param id"), http.StatusBadRequest) + } - // if !actionID return error - - id, _ := strconv.Atoi(actionID) - - if err := h.actionService.ToggleEnabled(id); err != nil { + if err := h.service.ToggleEnabled(actionID); err != nil { // encode error } - h.encoder.StatusResponse(ctx, w, nil, http.StatusCreated) + h.encoder.StatusResponse(r.Context(), w, nil, http.StatusCreated) +} + +func parseInt(s string) (int, error) { + u, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return 0, err + } + return int(u), nil } diff --git a/internal/http/auth.go b/internal/http/auth.go index f3b5e0c..ba1d4af 100644 --- a/internal/http/auth.go +++ b/internal/http/auth.go @@ -16,8 +16,15 @@ type authService interface { } type authHandler struct { - encoder encoder - authService authService + encoder encoder + service authService +} + +func newAuthHandler(encoder encoder, service authService) *authHandler { + return &authHandler{ + encoder: encoder, + service: service, + } } var ( @@ -49,7 +56,7 @@ func (h authHandler) login(w http.ResponseWriter, r *http.Request) { store.Options.SameSite = http.SameSiteStrictMode session, _ := store.Get(r, "user_session") - _, err := h.authService.Login(data.Username, data.Password) + _, err := h.service.Login(data.Username, data.Password) if err != nil { h.encoder.StatusResponse(ctx, w, nil, http.StatusUnauthorized) return diff --git a/internal/http/config.go b/internal/http/config.go index 33624f6..8c2d553 100644 --- a/internal/http/config.go +++ b/internal/http/config.go @@ -20,6 +20,10 @@ type configHandler struct { encoder encoder } +func newConfigHandler(encoder encoder) *configHandler { + return &configHandler{encoder: encoder} +} + func (h configHandler) Routes(r chi.Router) { r.Get("/", h.getConfig) } diff --git a/internal/http/download_client.go b/internal/http/download_client.go index c8b1348..e233528 100644 --- a/internal/http/download_client.go +++ b/internal/http/download_client.go @@ -18,8 +18,15 @@ type downloadClientService interface { } type downloadClientHandler struct { - encoder encoder - downloadClientService downloadClientService + encoder encoder + service downloadClientService +} + +func newDownloadClientHandler(encoder encoder, service downloadClientService) *downloadClientHandler { + return &downloadClientHandler{ + encoder: encoder, + service: service, + } } func (h downloadClientHandler) Routes(r chi.Router) { @@ -33,7 +40,7 @@ func (h downloadClientHandler) Routes(r chi.Router) { func (h downloadClientHandler) listDownloadClients(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - clients, err := h.downloadClientService.List() + clients, err := h.service.List() if err != nil { // } @@ -52,7 +59,7 @@ func (h downloadClientHandler) store(w http.ResponseWriter, r *http.Request) { return } - client, err := h.downloadClientService.Store(data) + client, err := h.service.Store(data) if err != nil { // encode error } @@ -72,7 +79,7 @@ func (h downloadClientHandler) test(w http.ResponseWriter, r *http.Request) { return } - err := h.downloadClientService.Test(data) + err := h.service.Test(data) if err != nil { // encode error h.encoder.StatusResponse(ctx, w, nil, http.StatusBadRequest) @@ -93,7 +100,7 @@ func (h downloadClientHandler) update(w http.ResponseWriter, r *http.Request) { return } - client, err := h.downloadClientService.Store(data) + client, err := h.service.Store(data) if err != nil { // encode error } @@ -111,7 +118,7 @@ func (h downloadClientHandler) delete(w http.ResponseWriter, r *http.Request) { id, _ := strconv.Atoi(clientID) - if err := h.downloadClientService.Delete(id); err != nil { + if err := h.service.Delete(id); err != nil { // encode error } diff --git a/internal/http/filter.go b/internal/http/filter.go index 4f9b420..6b7dba9 100644 --- a/internal/http/filter.go +++ b/internal/http/filter.go @@ -20,8 +20,15 @@ type filterService interface { } type filterHandler struct { - encoder encoder - filterService filterService + encoder encoder + service filterService +} + +func newFilterHandler(encoder encoder, service filterService) *filterHandler { + return &filterHandler{ + encoder: encoder, + service: service, + } } func (h filterHandler) Routes(r chi.Router) { @@ -35,7 +42,7 @@ func (h filterHandler) Routes(r chi.Router) { func (h filterHandler) getFilters(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - trackers, err := h.filterService.ListFilters() + trackers, err := h.service.ListFilters() if err != nil { // } @@ -51,7 +58,7 @@ func (h filterHandler) getByID(w http.ResponseWriter, r *http.Request) { id, _ := strconv.Atoi(filterID) - filter, err := h.filterService.FindByID(id) + filter, err := h.service.FindByID(id) if err != nil { h.encoder.StatusNotFound(ctx, w) return @@ -68,7 +75,7 @@ func (h filterHandler) storeFilterAction(w http.ResponseWriter, r *http.Request) id, _ := strconv.Atoi(filterID) - filter, err := h.filterService.FindByID(id) + filter, err := h.service.FindByID(id) if err != nil { // } @@ -87,7 +94,7 @@ func (h filterHandler) store(w http.ResponseWriter, r *http.Request) { return } - filter, err := h.filterService.Store(data) + filter, err := h.service.Store(data) if err != nil { // encode error return @@ -107,7 +114,7 @@ func (h filterHandler) update(w http.ResponseWriter, r *http.Request) { return } - filter, err := h.filterService.Update(data) + filter, err := h.service.Update(data) if err != nil { // encode error return @@ -124,7 +131,7 @@ func (h filterHandler) delete(w http.ResponseWriter, r *http.Request) { id, _ := strconv.Atoi(filterID) - if err := h.filterService.Delete(id); err != nil { + if err := h.service.Delete(id); err != nil { // return err } diff --git a/internal/http/indexer.go b/internal/http/indexer.go index 17e21cb..c4fd249 100644 --- a/internal/http/indexer.go +++ b/internal/http/indexer.go @@ -20,8 +20,17 @@ type indexerService interface { } type indexerHandler struct { - encoder encoder - indexerService indexerService + encoder encoder + service indexerService + ircSvc ircService +} + +func newIndexerHandler(encoder encoder, service indexerService, ircSvc ircService) *indexerHandler { + return &indexerHandler{ + encoder: encoder, + service: service, + ircSvc: ircSvc, + } } func (h indexerHandler) Routes(r chi.Router) { @@ -36,7 +45,7 @@ func (h indexerHandler) Routes(r chi.Router) { func (h indexerHandler) getSchema(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - indexers, err := h.indexerService.GetTemplates() + indexers, err := h.service.GetTemplates() if err != nil { // } @@ -45,21 +54,20 @@ func (h indexerHandler) getSchema(w http.ResponseWriter, r *http.Request) { } func (h indexerHandler) store(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - data domain.Indexer - ) + var data domain.Indexer if err := json.NewDecoder(r.Body).Decode(&data); err != nil { return } - indexer, err := h.indexerService.Store(data) + indexer, err := h.service.Store(data) if err != nil { // + h.encoder.StatusResponse(r.Context(), w, nil, http.StatusBadRequest) + return } - h.encoder.StatusResponse(ctx, w, indexer, http.StatusCreated) + h.encoder.StatusResponse(r.Context(), w, indexer, http.StatusCreated) } func (h indexerHandler) update(w http.ResponseWriter, r *http.Request) { @@ -72,7 +80,7 @@ func (h indexerHandler) update(w http.ResponseWriter, r *http.Request) { return } - indexer, err := h.indexerService.Update(data) + indexer, err := h.service.Update(data) if err != nil { // } @@ -88,7 +96,7 @@ func (h indexerHandler) delete(w http.ResponseWriter, r *http.Request) { id, _ := strconv.Atoi(idParam) - if err := h.indexerService.Delete(id); err != nil { + if err := h.service.Delete(id); err != nil { // return err } @@ -98,7 +106,7 @@ func (h indexerHandler) delete(w http.ResponseWriter, r *http.Request) { func (h indexerHandler) getAll(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - indexers, err := h.indexerService.GetAll() + indexers, err := h.service.GetAll() if err != nil { // } @@ -109,7 +117,7 @@ func (h indexerHandler) getAll(w http.ResponseWriter, r *http.Request) { func (h indexerHandler) list(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - indexers, err := h.indexerService.List() + indexers, err := h.service.List() if err != nil { // } diff --git a/internal/http/irc.go b/internal/http/irc.go index bba1921..e662d2e 100644 --- a/internal/http/irc.go +++ b/internal/http/irc.go @@ -21,8 +21,15 @@ type ircService interface { } type ircHandler struct { - encoder encoder - ircService ircService + encoder encoder + service ircService +} + +func newIrcHandler(encoder encoder, service ircService) *ircHandler { + return &ircHandler{ + encoder: encoder, + service: service, + } } func (h ircHandler) Routes(r chi.Router) { @@ -38,7 +45,7 @@ func (h ircHandler) Routes(r chi.Router) { func (h ircHandler) listNetworks(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - networks, err := h.ircService.ListNetworks(ctx) + networks, err := h.service.ListNetworks(ctx) if err != nil { // } @@ -54,7 +61,7 @@ func (h ircHandler) getNetworkByID(w http.ResponseWriter, r *http.Request) { id, _ := strconv.Atoi(networkID) - network, err := h.ircService.GetNetworkByID(int64(id)) + network, err := h.service.GetNetworkByID(int64(id)) if err != nil { // } @@ -72,9 +79,11 @@ func (h ircHandler) storeNetwork(w http.ResponseWriter, r *http.Request) { return } - err := h.ircService.StoreNetwork(&data) + err := h.service.StoreNetwork(&data) if err != nil { // + h.encoder.StatusResponse(ctx, w, nil, http.StatusBadRequest) + return } h.encoder.StatusResponse(ctx, w, nil, http.StatusCreated) @@ -93,7 +102,7 @@ func (h ircHandler) storeChannel(w http.ResponseWriter, r *http.Request) { return } - err := h.ircService.StoreChannel(int64(id), &data) + err := h.service.StoreChannel(int64(id), &data) if err != nil { // } @@ -107,7 +116,7 @@ func (h ircHandler) stopNetwork(w http.ResponseWriter, r *http.Request) { networkID = chi.URLParam(r, "networkID") ) - err := h.ircService.StopNetwork(networkID) + err := h.service.StopNetwork(networkID) if err != nil { // } @@ -123,7 +132,7 @@ func (h ircHandler) deleteNetwork(w http.ResponseWriter, r *http.Request) { id, _ := strconv.Atoi(networkID) - err := h.ircService.DeleteNetwork(ctx, int64(id)) + err := h.service.DeleteNetwork(ctx, int64(id)) if err != nil { // } diff --git a/internal/http/service.go b/internal/http/server.go similarity index 65% rename from internal/http/service.go rename to internal/http/server.go index f3e6fba..c105730 100644 --- a/internal/http/service.go +++ b/internal/http/server.go @@ -64,56 +64,19 @@ func (s Server) Handler() http.Handler { fileSystem.ServeHTTP(w, r) }) - authHandler := authHandler{ - encoder: encoder, - authService: s.authService, - } - - r.Route("/api/auth", authHandler.Routes) + r.Route("/api/auth", newAuthHandler(encoder, s.authService).Routes) r.Group(func(r chi.Router) { r.Use(IsAuthenticated) - actionHandler := actionHandler{ - encoder: encoder, - actionService: s.actionService, - } - - r.Route("/api/actions", actionHandler.Routes) - - downloadClientHandler := downloadClientHandler{ - encoder: encoder, - downloadClientService: s.downloadClientService, - } - - r.Route("/api/download_clients", downloadClientHandler.Routes) - - filterHandler := filterHandler{ - encoder: encoder, - filterService: s.filterService, - } - - r.Route("/api/filters", filterHandler.Routes) - - ircHandler := ircHandler{ - encoder: encoder, - ircService: s.ircService, - } - - r.Route("/api/irc", ircHandler.Routes) - - indexerHandler := indexerHandler{ - encoder: encoder, - indexerService: s.indexerService, - } - - r.Route("/api/indexer", indexerHandler.Routes) - - configHandler := configHandler{ - encoder: encoder, - } - - r.Route("/api/config", configHandler.Routes) + r.Route("/api", func(r chi.Router) { + r.Route("/actions", newActionHandler(encoder, s.actionService).Routes) + r.Route("/config", newConfigHandler(encoder).Routes) + r.Route("/download_clients", newDownloadClientHandler(encoder, s.downloadClientService).Routes) + r.Route("/filters", newFilterHandler(encoder, s.filterService).Routes) + r.Route("/irc", newIrcHandler(encoder, s.ircService).Routes) + r.Route("/indexer", newIndexerHandler(encoder, s.indexerService, s.ircService).Routes) + }) }) //r.HandleFunc("/*", handler.ServeHTTP) diff --git a/internal/indexer/definitions/alpharatio.yaml b/internal/indexer/definitions/alpharatio.yaml index 1d91556..74f186d 100644 --- a/internal/indexer/definitions/alpharatio.yaml +++ b/internal/indexer/definitions/alpharatio.yaml @@ -14,31 +14,46 @@ supports: source: gazelle settings: - name: authkey - type: text + type: secret label: Auth key - tooltip: Right click DL on a torrent and get the authkey. - description: Right click DL on a torrent and get the authkey. + help: Right click DL on a torrent and get the authkey. - name: torrent_pass - type: text + type: secret label: Torrent pass - tooltip: Right click DL on a torrent and get the torrent_pass. - description: Right click DL on a torrent and get the torrent_pass. + help: Right click DL on a torrent and get the torrent_pass. irc: network: AlphaRatio - server: irc.alpharatio.cc:6697 + server: irc.alpharatio.cc port: 6697 + tls: true channels: - "#Announce" announcers: - Voyager + settings: + - name: nickserv.account + type: text + required: true + label: NickServ Account + help: NickServ account. Make sure to group your user and bot. Eg. user-bot + - name: nickserv.password + type: secret + required: true + label: NickServ Password + help: NickServ password + - name: invite_command + type: secret + default: "Voyager autobot USERNAME IRCKey" + required: false + label: Invite command + help: Invite auth with Voyager. parse: type: multi lines: - - - test: - - "[New Release]-[MovieHD]-[War.For.The.Planet.Of.The.Apes.2017.INTERNAL.1080p.BluRay.CRF.x264-SAPHiRE]-[URL]-[ https://alpharatio.cc/torrents.php?id=699463 ]-[ 699434 ]-[ Uploaded 2 Mins, 59 Secs after pre. ]" + - test: + - "[New Release]-[MovieHD]-[That.Movie.2017.INTERNAL.1080p.BluRay.CRF.x264-GROUP]-[URL]-[ https://alpharatio.cc/torrents.php?id=000000 ]-[ 000000 ]-[ Uploaded 2 Mins, 59 Secs after pre. ]" pattern: \[New Release\]-\[(.*)\]-\[(.*)\]-\[URL\]-\[ (https?://.*)id=\d+ \]-\[ (\d+) \](?:-\[ Uploaded (.*) after pre. ])? vars: - category @@ -46,9 +61,8 @@ parse: - baseUrl - torrentId - preTime - - - test: - - "[AutoDL]-[MovieHD]-[699434]-[ 1 | 10659 | 1 | 1 ]-[War.For.The.Planet.Of.The.Apes.2017.INTERNAL.1080p.BluRay.CRF.x264-SAPHiRE]" + - test: + - "[AutoDL]-[MovieHD]-[000000]-[ 1 | 10659 | 1 | 1 ]-[That.Movie.2017.INTERNAL.1080p.BluRay.CRF.x264-GROUP]" pattern: \[AutoDL\]-\[.*\]-\[.*\]-\[ ([01]) \| (\d+) \| ([01]) \| ([01]) \]-\[.+\] vars: - scene diff --git a/internal/indexer/definitions/beyondhd.yaml b/internal/indexer/definitions/beyondhd.yaml index 70db55b..03545c8 100644 --- a/internal/indexer/definitions/beyondhd.yaml +++ b/internal/indexer/definitions/beyondhd.yaml @@ -13,28 +13,44 @@ supports: - rss source: UNIT3D (F3NIX) settings: - - name: passkey - type: text - label: Passkey - tooltip: The passkey in your BeyondHD RSS feed. - description: "Go to your profile and copy and paste your RSS link to extract the rsskey." + - name: rsskey + type: secret + label: RSS key + help: "Go to your profile, My Security, RSS Key and copy RSS key." irc: network: BeyondHD-IRC - server: irc.beyond-hd.me:6697 + server: irc.beyond-hd.me port: 6697 + tls: true channels: - "#bhd_announce" announcers: - Willie - Millie + settings: + - name: nickserv.account + type: text + required: true + label: NickServ Account + help: NickServ account. Make sure to group your user and bot. Eg. user|autodl + - name: nickserv.password + type: secret + required: true + label: NickServ Password + help: NickServ password + - name: invite_command + type: secret + default: "Millie announce ircKey" + required: true + label: Invite command + help: Invite auth with Millie. parse: type: single lines: - - - test: - - "New Torrent: Orange.Is.the.New.Black.S01.1080p.Blu-ray.AVC.DTS-HD.MA.5.1-Test Category: TV By: Uploader Size: 137.73 GB Link: https://beyond-hd.me/details.php?id=25918" + - test: + - "New Torrent: That.Show.S01.1080p.Blu-ray.AVC.DTS-HD.MA.5.1-Test Category: TV By: Uploader Size: 137.73 GB Link: https://beyond-hd.me/details.php?id=00000" pattern: 'New Torrent:(.*)Category:(.*)By:(.*)Size:(.*)Link: https?\:\/\/([^\/]+\/).*[&\?]id=(\d+)' vars: - torrentName diff --git a/internal/indexer/definitions/btn.yaml b/internal/indexer/definitions/btn.yaml index 98e2c93..d8914cc 100644 --- a/internal/indexer/definitions/btn.yaml +++ b/internal/indexer/definitions/btn.yaml @@ -13,42 +13,57 @@ supports: - rss source: gazelle settings: - - name: authkey - type: text - label: Auth key - description: Right click DL on a torrent and get the authkey. - - name: torrent_pass - type: text - label: Torrent pass - description: Right click DL on a torrent and get the torrent_pass. + - name: authkey + type: secret + label: Auth key + help: Right click DL on a torrent and get the authkey. + - name: torrent_pass + type: secret + label: Torrent pass + help: Right click DL on a torrent and get the torrent_pass. irc: network: BroadcasTheNet - server: irc.broadcasthenet.net:6697 + server: irc.broadcasthenet.net port: 6697 + tls: true channels: - "#BTN-Announce" announcers: - Barney + settings: + - name: nickserv.account + type: text + required: true + label: NickServ Account + help: NickServ account. Make sure to group your user and bot. Eg. user|autodl + - name: nickserv.password + type: secret + required: true + label: NickServ Password + help: NickServ password + - name: invite_command + type: secret + default: "CableGuy IDENTIFY USERNAME IRCKey" + required: true + label: Invite command + help: Invite auth with CableGuy. parse: type: multi lines: - - - test: - - "NOW BROADCASTING! [ Lost S06E07 720p WEB-DL DD 5.1 H.264 - LP ]" + - test: + - "NOW BROADCASTING! [ The Show S06E07 720p WEB-DL DD 5.1 H.264 - LP ]" pattern: ^NOW BROADCASTING! \[(.*)\] vars: - torrentName - - - test: - - "[ Title: S06E07 ] [ Series: Lost ]" + - test: + - "[ Title: S06E07 ] [ Series: The Show ]" pattern: '^\[ Title: (.*) \] \[ Series: (.*) \]' vars: - title - name1 - - - test: + - test: - "[ 2010 ] [ Episode ] [ MKV | x264 | WEB ] [ Uploader: Uploader1 ]" pattern: '^(?:\[ (\d+) \] )?\[ (.*) \] \[ (.*) \] \[ Uploader: (.*?) \](?: \[ Pretime: (.*) \])?' vars: @@ -57,10 +72,9 @@ parse: - tags - uploader - preTime - - - test: + - test: - "[ https://XXXXXXXXX/torrents.php?id=7338 / https://XXXXXXXXX/torrents.php?action=download&id=9116 ]" - pattern: ^\[ .* / (https?://.*id=\d+) \] + pattern: ^\[ .* \/ (https?:\/\/.*id=\d+) \] vars: - baseUrl diff --git a/internal/indexer/definitions/emp.yaml b/internal/indexer/definitions/emp.yaml index d92f5a4..6c3b022 100644 --- a/internal/indexer/definitions/emp.yaml +++ b/internal/indexer/definitions/emp.yaml @@ -14,28 +14,39 @@ supports: source: gazelle settings: - name: authkey - type: text + type: secret label: Auth key - description: Right click DL on a torrent and get the authkey. + help: Right click DL on a torrent and get the authkey. - name: torrent_pass - type: text + type: secret label: Torrent pass - description: Right click DL on a torrent and get the torrent_pass. + help: Right click DL on a torrent and get the torrent_pass. irc: network: DigitalIRC - server: irc.empornium.is:6697 + server: irc.empornium.is port: 6697 + tls: true channels: - "#empornium-announce" announcers: - "^Wizard^" + settings: + - name: nickserv.account + type: text + required: true + label: NickServ Account + help: NickServ account. Make sure to group your user and bot. Eg. user_bot. Must have staff permission first. + - name: nickserv.password + type: secret + required: true + label: NickServ Password + help: NickServ password parse: type: single lines: - - - pattern: '^(.*?) - Size: ([0-9]+?.*?) - Uploader: (.*?) - Tags: (.*?) - (https://.*torrents.php\?)id=(.*)$' + - pattern: '^(.*?) - Size: ([0-9]+?.*?) - Uploader: (.*?) - Tags: (.*?) - (https://.*torrents.php\?)id=(.*)$' vars: - torrentName - torrentSize diff --git a/internal/indexer/definitions/filelist.yaml b/internal/indexer/definitions/filelist.yaml index 90fe79f..ee77b73 100644 --- a/internal/indexer/definitions/filelist.yaml +++ b/internal/indexer/definitions/filelist.yaml @@ -14,28 +14,38 @@ supports: source: custom settings: - name: passkey - type: text + type: secret label: Passkey - tooltip: The passkey in your profile. - description: "The passkey in your profile." + help: "The passkey in your profile." irc: network: FileList - server: irc.filelist.io:6697 + server: irc.filelist.io port: 6697 + tls: true channels: - "#announce" announcers: - Announce + settings: + - name: nickserv.account + type: text + required: true + label: NickServ Account + help: NickServ account. Make sure to group your user and bot. Eg. user_dl + - name: nickserv.password + type: secret + required: false + label: NickServ Password + help: NickServ password parse: type: single lines: - - - test: - - 'New Torrent: This.Really.Old.Movie.1965.DVDRip.DD1.0.x264 -- [Filme SD] [1.91 GB] -- https://filelist.io/details.php?id=746781 -- by uploader1' - - 'New Torrent: This.New.Movie.2021.1080p.Blu-ray.AVC.DTS-HD.MA.5.1-BEATRIX -- [FreeLeech!] -- [Filme Blu-Ray] [26.78 GB] -- https://filelist.io/details.php?id=746782 -- by uploader1' - - 'New Torrent: This.New.Movie.2021.1080p.Remux.AVC.DTS-HD.MA.5.1-playBD -- [FreeLeech!] -- [Internal!] -- [Filme Blu-Ray] [17.69 GB] -- https://filelist.io/details.php?id=746789 -- by uploader1' + - test: + - 'New Torrent: This.Really.Old.Movie.1965.DVDRip.DD1.0.x264 -- [Filme SD] [1.91 GB] -- https://filelist.io/details.php?id=000000 -- by uploader1' + - 'New Torrent: This.New.Movie.2021.1080p.Blu-ray.AVC.DTS-HD.MA.5.1-BEATRIX -- [FreeLeech!] -- [Filme Blu-Ray] [26.78 GB] -- https://filelist.io/details.php?id=000000 -- by uploader1' + - 'New Torrent: This.New.Movie.2021.1080p.Remux.AVC.DTS-HD.MA.5.1-playBD -- [FreeLeech!] -- [Internal!] -- [Filme Blu-Ray] [17.69 GB] -- https://filelist.io/details.php?id=000000 -- by uploader1' pattern: 'New Torrent: (.*?) (?:-- \[(FreeLeech!)] )?(?:-- \[(Internal!)] )?-- \[(.*)] \[(.*)] -- (https?:\/\/filelist.io\/).*id=(.*) -- by (.*)' vars: - torrentName diff --git a/internal/indexer/definitions/gazellegames.yaml b/internal/indexer/definitions/gazellegames.yaml index 90804b4..5d28d0d 100644 --- a/internal/indexer/definitions/gazellegames.yaml +++ b/internal/indexer/definitions/gazellegames.yaml @@ -14,32 +14,47 @@ supports: source: gazelle settings: - name: authkey - type: text + type: secret label: Auth key - tooltip: Right click DL on a torrent and get the authkey. - description: Right click DL on a torrent and get the authkey. + help: Right click DL on a torrent and get the authkey. - name: torrent_pass - type: text + type: secret label: Torrent pass - tooltip: Right click DL on a torrent and get the torrent_pass. - description: Right click DL on a torrent and get the torrent_pass. + help: Right click DL on a torrent and get the torrent_pass. irc: network: GGn - server: irc.gazellegames.net:7000 + server: irc.gazellegames.net port: 7000 + tls: true channels: - "#GGn-Announce" announcers: - Vertigo + settings: + - name: nickserv.account + type: text + required: true + label: NickServ Account + help: NickServ account. Make sure to group your user and bot. Eg. user|bot + - name: nickserv.password + type: secret + required: true + label: NickServ Password + help: NickServ password + - name: invite_command + type: secret + default: "Vertigo ENTER #GGn-Announce USERNAME IRCKey" + required: true + label: Invite command + help: Invite auth with Vertigo. parse: type: single lines: - - - test: - - "Uploader :-: Nintendo 3DS :-: Yo-Kai.Watch.KOR.3DS-BigBlueBox in Yo-kai Watch [2013] ::Korean, Multi-Region, Scene:: https://gazellegames.net/torrents.php?torrentid=78851 - adventure, role_playing_game, nintendo;" - - "Uploader :-: Windows :-: Warriors.Wrath.Evil.Challenge-HI2U in Warriors' Wrath [2016] ::English, Scene:: FREELEECH! :: https://gazellegames.net/torrents.php?torrentid=78902 - action, adventure, casual, indie, role.playing.game;" + - test: + - "Uploader :-: Nintendo 3DS :-: Cool.Game.KOR.3DS-BigBlueBox in Cool Game [2013] ::Korean, Multi-Region, Scene:: https://gazellegames.net/torrents.php?torrentid=00000 - adventure, role_playing_game, nintendo;" + - "Uploader :-: Windows :-: Other.Game-HI2U in Other Game [2016] ::English, Scene:: FREELEECH! :: https://gazellegames.net/torrents.php?torrentid=00000 - action, adventure, casual, indie, role.playing.game;" pattern: '^(.+) :-: (.+) :-: (.+) \[(\d+)\] ::(.+?):: ?(.+? ::)? https?:\/\/([^\/]+\/)torrents.php\?torrentid=(\d+) ?-? ?(.*?)?;?$' vars: - uploader diff --git a/internal/indexer/definitions/hd-torrents.yaml b/internal/indexer/definitions/hd-torrents.yaml index cd09575..dee2971 100644 --- a/internal/indexer/definitions/hd-torrents.yaml +++ b/internal/indexer/definitions/hd-torrents.yaml @@ -14,26 +14,37 @@ supports: - rss source: xbtit settings: - - name: cookie - type: text - label: Cookie - description: "FireFox -> Preferences -> Privacy -> Show Cookies and find the uid and pass cookies. Example: uid=1234; pass=asdf12347asdf13" + - name: cookie + type: text + label: Cookie + help: "Check how to get cookies in your browser and find the uid and pass cookies. Example: uid=1234; pass=asdf12347asdf13" irc: network: P2P-NET - server: irc.p2p-network.net:6697 + server: irc.p2p-network.net port: 6697 + tls: true channels: - "#HD-Torrents.Announce" announcers: - HoboLarry + settings: + - name: nickserv.account + type: text + required: true + label: NickServ Account + help: NickServ account. Make sure to group your user and bot. Eg. user|bot + - name: nickserv.password + type: secret + required: true + label: NickServ Password + help: NickServ password parse: type: single lines: - - - test: - - "New Torrent in category [XXX/Blu-ray] Erotische Fantasien 3D (2008) Blu-ray 1080p AVC DTS-HD MA 7 1 (14.60 GB) uploaded! Download: https://hd-torrents.org/download.php?id=806bc36530d146969d300c5352483a5e6e0639e9" + - test: + - "New Torrent in category [Movies/Remux] That Movie (2008) Blu-ray 1080p REMUX AVC DTS-HD MA 7 1 (14.60 GB) uploaded! Download: https://hd-torrents.org/download.php?id=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" pattern: 'New Torrent in category \[([^\]]*)\] (.*) \(([^\)]*)\) uploaded! Download\: https?\:\/\/([^\/]+\/).*[&\?]id=([a-f0-9]+)' vars: - category diff --git a/internal/indexer/definitions/iptorrents.yaml b/internal/indexer/definitions/iptorrents.yaml index a6b821d..b22a824 100644 --- a/internal/indexer/definitions/iptorrents.yaml +++ b/internal/indexer/definitions/iptorrents.yaml @@ -15,29 +15,38 @@ supports: source: unknown settings: - name: passkey - type: text + type: secret label: Passkey - tooltip: Copy the passkey from your details page - description: "Copy the passkey from your details page." + help: "Copy the passkey from your details page." irc: network: IPTorrents - server: irc.iptorrents.com:6697 + server: irc.iptorrents.com port: 6697 + tls: true channels: - "#ipt.announce" - - "#ipt.announce2" announcers: - IPT - FunTimes + settings: + - name: nickserv.account + type: text + required: true + label: NickServ Account + help: NickServ account. Make sure to group your user and bot. Eg. user_autodl + - name: nickserv.password + type: secret + required: false + label: NickServ Password + help: NickServ password parse: type: single lines: - - - test: - - "[Movie/XXX] Audrey Bitoni HD Pack FREELEECH - http://www.iptorrents.com/details.php?id=789421 - 14.112 GB" - - "[Movies/XviD] The First Men In The Moon 2010 DVDRip XviD-VoMiT - http://www.iptorrents.com/details.php?id=396589 - 716.219 MB" + - test: + - "[Movies/XviD] The Movie 2010 DVDRip XviD-GROUP FREELEECH - http://www.iptorrents.com/details.php?id=000000 - 716.219 MB" + - "[Movies/XviD] The Movie 2010 DVDRip XviD-GROUP - http://www.iptorrents.com/details.php?id=000000 - 716.219 MB" pattern: '^\[([^\]]*)](.*?)\s*(FREELEECH)*\s*-\s+https?\:\/\/([^\/]+).*[&\?]id=(\d+)\s*-(.*)' vars: - category diff --git a/internal/indexer/definitions/nebulance.yaml b/internal/indexer/definitions/nebulance.yaml index 186b98e..efac41f 100644 --- a/internal/indexer/definitions/nebulance.yaml +++ b/internal/indexer/definitions/nebulance.yaml @@ -13,33 +13,41 @@ supports: - rss source: gazelle settings: - - name: authkey - type: text - label: Auth key - tooltip: Right click DL on a torrent and get the authkey. - description: Right click DL on a torrent and get the authkey. - - name: torrent_pass - type: text - label: Torrent pass - tooltip: Right click DL on a torrent and get the torrent_pass. - description: Right click DL on a torrent and get the torrent_pass. + - name: authkey + type: secret + label: Auth key + help: Right click DL on a torrent and get the authkey. + - name: torrent_pass + type: secret + label: Torrent pass + help: Right click DL on a torrent and get the torrent_pass. irc: network: Nebulance - server: irc.nebulance.cc:6697 + server: irc.nebulance.cc port: 6697 channels: - "#nbl-announce" announcers: - DRADIS + settings: + - name: nickserv.account + type: text + required: true + label: NickServ Account + help: NickServ account. Make sure to group your user and bot. Eg. user|bot + - name: nickserv.password + type: secret + required: true + label: NickServ Password + help: NickServ password parse: type: single lines: - - - test: - - "[Episodes] The Vet Life - S02E08 [WebRip / x264 / MKV / 720p / HD / VLAD / The.Vet.Life.S02E08.Tuskegee.Reunion.720p.ANPL.WEBRip.AAC2.0.x264-VLAD.mkv] [702.00 MB - Uploader: UPLOADER] - http://nebulance.io/torrents.php?id=147 [Tags: comedy,subtitles,cbs]" - - "[Seasons] Police Interceptors - S10 [HDTV / x264 / MKV / MP4 / 480p / SD / BTN / Police.Interceptors.S10.HDTV.x264-BTN] [5.27 GB - Uploader: UPLOADER] - http://nebulance.io/torrents.php?id=1472 [Tags: comedy,subtitles,cbs]" + - test: + - "[Episodes] The Show - S02E08 [WebRip / x264 / MKV / 720p / HD / VLAD / The.Show.S02E08.Episode.Name.720p.ANPL.WEBRip.AAC2.0.x264-GROUP.mkv] [702.00 MB - Uploader: UPLOADER] - http://nebulance.io/torrents.php?id=000 [Tags: comedy,subtitles,cbs]" + - "[Seasons] Other Show - S10 [HDTV / x264 / MKV / MP4 / 480p / SD / BTN / Other.Show.S10.HDTV.x264-GROUP] [5.27 GB - Uploader: UPLOADER] - http://nebulance.io/torrents.php?id=0000 [Tags: comedy,subtitles,cbs]" pattern: '\[(.*?)\] (.*?) \[(.*?)\] \[(.*?) - Uploader: (.*?)\] - (https?://.*)id=(\d+) \[Tags: (.*)\]' vars: - category diff --git a/internal/indexer/definitions/orpheus.yaml b/internal/indexer/definitions/orpheus.yaml index 49a71f6..4bf2e62 100644 --- a/internal/indexer/definitions/orpheus.yaml +++ b/internal/indexer/definitions/orpheus.yaml @@ -13,33 +13,48 @@ supports: - rss source: gazelle settings: - - name: authkey - type: text - label: Auth key - tooltip: Right click DL on a torrent and get the authkey. - description: Right click DL on a torrent and get the authkey. - - name: torrent_pass - type: text - label: Torrent pass - tooltip: Right click DL on a torrent and get the torrent_pass. - description: Right click DL on a torrent and get the torrent_pass. + - name: authkey + type: text + label: Auth key + help: Right click DL on a torrent and get the authkey. + - name: torrent_pass + type: text + label: Torrent pass + help: Right click DL on a torrent and get the torrent_pass. irc: network: Orpheus - server: irc.orpheus.network:7000 + server: irc.orpheus.network port: 7000 + tls: true channels: - "#announce" announcers: - hermes + settings: + - name: nickserv.account + type: text + required: true + label: NickServ Account + help: NickServ account. Make sure to group your user and bot. Eg. user|bot + - name: nickserv.password + type: secret + required: true + label: NickServ Password + help: NickServ password + - name: invite_command + type: secret + default: "hermes enter #announce USERNAME IRCKey" + required: true + label: Invite command + help: Invite auth with Hermes. parse: type: single lines: - - - test: - - "TORRENT: Todd Edwards - You Came To Me [2002] [Single] - FLAC / Lossless / WEB - 2000s,house,uk.garage,garage.house - https://orpheus.network/torrents.php?id=756102 / https://orpheus.network/torrents.php?action=download&id=1647868" - - "TORRENT: THE BOOK [2021] [Album] - FLAC / Lossless / CD - - https://orpheus.network/torrents.php?id=693523 / https://orpheus.network/torrents.php?action=download&id=1647867" + - test: + - "TORRENT: That Artist - Albuum [2002] [Single] - FLAC / Lossless / WEB - 2000s,house,uk.garage,garage.house - https://orpheus.network/torrents.php?id=000000 / https://orpheus.network/torrents.php?action=download&id=0000000" + - "TORRENT: Something [2021] [Album] - FLAC / Lossless / CD - - https://orpheus.network/torrents.php?id=000000 / https://orpheus.network/torrents.php?action=download&id=0000000" pattern: 'TORRENT: (.*) - (.*) - https?://.* / (https?://.*id=\d+)' vars: - torrentName diff --git a/internal/indexer/definitions/ptp.yaml b/internal/indexer/definitions/ptp.yaml index abcab69..41fa8aa 100644 --- a/internal/indexer/definitions/ptp.yaml +++ b/internal/indexer/definitions/ptp.yaml @@ -13,33 +13,48 @@ supports: - rss source: gazelle settings: - - name: authkey - type: text - label: Auth key - tooltip: Right click DL on a torrent and get the authkey. - description: Right click DL on a torrent and get the authkey. - - name: torrent_pass - type: text - label: Torrent pass - tooltip: Right click DL on a torrent and get the torrent_pass. - description: Right click DL on a torrent and get the torrent_pass. + - name: authkey + type: secret + label: Auth key + help: Right click DL on a torrent and get the authkey. + - name: torrent_pass + type: secret + label: Torrent pass + help: Right click DL on a torrent and get the torrent_pass. irc: network: PassThePopcorn - server: irc.passthepopcorn.me:7000 + server: irc.passthepopcorn.me port: 7000 + tls: true channels: - "#ptp-announce" announcers: - Hummingbird + settings: + - name: nickserv.account + type: text + required: true + label: NickServ Account + help: NickServ account. Make sure to group your user and bot. Eg. user|autodl + - name: nickserv.password + type: secret + required: true + label: NickServ Password + help: NickServ password + - name: invite_command + type: secret + default: "Hummingbird ENTER USERNAME IRCKey #ptp-announce" + required: true + label: Invite command + help: Invite auth with Hummingbird. parse: type: single lines: - - - test: - - "Irene Huss - Nattrond AKA The Night Round [2008] by Anders Engström - XviD / DVD / AVI / 640x352 - http://passthepopcorn.me/torrents.php?id=51627 / http://passthepopcorn.me/torrents.php?action=download&id=97333 - crime, drama, mystery" - - "Dirty Rotten Scoundrels [1988] by Frank Oz - x264 / Blu-ray / MKV / 720p - http://passthepopcorn.me/torrents.php?id=10735 / http://passthepopcorn.me/torrents.php?action=download&id=97367 - comedy, crime" + - test: + - "That Movie [2008] by Director - XviD / DVD / AVI / 640x352 - http://passthepopcorn.me/torrents.php?id=00000 / http://passthepopcorn.me/torrents.php?action=download&id=00000 - crime, drama, mystery" + - "Some Old Movie [1988] by Director - x264 / Blu-ray / MKV / 720p - http://passthepopcorn.me/torrents.php?id=00000 / http://passthepopcorn.me/torrents.php?action=download&id=00000 - comedy, crime" pattern: '^(.*)-\s*https?:.*[&\?]id=.*https?\:\/\/([^\/]+\/).*[&\?]id=(\d+)\s*-\s*(.*)' vars: - torrentName diff --git a/internal/indexer/definitions/red.yaml b/internal/indexer/definitions/red.yaml index c0cfe72..128940f 100644 --- a/internal/indexer/definitions/red.yaml +++ b/internal/indexer/definitions/red.yaml @@ -13,33 +13,48 @@ supports: - rss source: gazelle settings: - - name: authkey - type: text - label: Auth key - tooltip: Right click DL on a torrent and get the authkey. - description: Right click DL on a torrent and get the authkey. - - name: torrent_pass - type: text - label: Torrent pass - tooltip: Right click DL on a torrent and get the torrent_pass. - description: Right click DL on a torrent and get the torrent_pass. + - name: authkey + type: secret + label: Auth key + help: Right click DL on a torrent and get the authkey. + - name: torrent_pass + type: secret + label: Torrent pass + help: Right click DL on a torrent and get the torrent_pass. irc: network: Scratch-Network - server: irc.scratch-network.net:6697 + server: irc.scratch-network.net port: 6697 + tls: true channels: - "#red-announce" announcers: - Drone + settings: + - name: nickserv.account + type: text + required: true + label: NickServ Account + help: NickServ account. Make sure to group your user and bot. Eg. user-autodl + - name: nickserv.password + type: secret + required: true + label: NickServ Password + help: NickServ password + - name: invite_command + type: secret + default: "Drone enter #red-announce USERNAME IRCKey" + required: true + label: Invite command + help: Invite auth with Drone. parse: type: single lines: - - - test: - - "JR Get Money - Nobody But You [2008] [Single] - FLAC / Lossless / Log / 100% / Cue / CD - https://redacted.ch/torrents.php?id=1592366 / https://redacted.ch/torrents.php?action=download&id=3372962 - hip.hop,rhythm.and.blues,2000s" - - "Johann Sebastian Bach performed by Festival Strings Lucerne under Rudolf Baumgartner - Brandenburg Concertos 5 and 6, Suite No 2 [1991] [Album] - FLAC / Lossless / Log / 100% / Cue / CD - https://redacted.ch/torrents.php?id=1592367 / https://redacted.ch/torrents.php?action=download&id=3372963 - classical" + - test: + - "Artist - Albumname [2008] [Single] - FLAC / Lossless / Log / 100% / Cue / CD - https://redacted.ch/torrents.php?id=0000000 / https://redacted.ch/torrents.php?action=download&id=0000000 - hip.hop,rhythm.and.blues,2000s" + - "A really long name here - Concertos 5 and 6, Suite No 2 [1991] [Album] - FLAC / Lossless / Log / 100% / Cue / CD - https://redacted.ch/torrents.php?id=0000000 / https://redacted.ch/torrents.php?action=download&id=0000000 - classical" pattern: '^(.*)\s+-\s+https?:.*[&\?]id=.*https?\:\/\/([^\/]+\/).*[&\?]id=(\d+)\s*-\s*(.*)' vars: - torrentName diff --git a/internal/indexer/definitions/superbits.yaml b/internal/indexer/definitions/superbits.yaml index 7c59310..cf92c4c 100644 --- a/internal/indexer/definitions/superbits.yaml +++ b/internal/indexer/definitions/superbits.yaml @@ -14,27 +14,37 @@ supports: source: rartracker settings: - name: passkey - type: text + type: secret label: Passkey - tooltip: Copy the passkey from the /rss page - description: "Copy the passkey from the /rss page." + help: "Copy the passkey from the /rss page." irc: network: SuperBits - server: irc.superbits.org:6697 + server: irc.superbits.org port: 6697 + tls: true channels: - "#autodl" announcers: - SuperBits + settings: + - name: nickserv.account + type: text + required: true + label: NickServ Account + help: NickServ account. Make sure to group your user and bot. Eg. user-bot + - name: nickserv.password + type: secret + required: false + label: NickServ Password + help: NickServ password parse: type: single lines: - - - test: - - "-[archive Film 1080]2[Asterix.Et.La.Surprise.De.Cesar.1985.FRENCH.1080p.BluRay.x264-TSuNaMi]3[844551]4[Size: 4.41 GB]5[FL: no]6[Scene: yes]" - - "-[new TV]2[Party.Down.South.S05E05.720p.WEB.h264-DiRT]3[844557]4[Size: 964.04 MB]5[FL: no]6[Scene: yes]7[Pred 1m 30s ago]" + - test: + - "-[archive Film 1080]2[A.Movie.1985.FRENCH.1080p.BluRay.x264-GROUP]3[000000]4[Size: 4.41 GB]5[FL: no]6[Scene: yes]" + - "-[new TV]2[Some.Show.S05E05.720p.WEB.h264-GROUP]3[000000]4[Size: 964.04 MB]5[FL: no]6[Scene: yes]7[Pred 1m 30s ago]" pattern: '\-\[(.*)\]2\[(.*)\]3\[(\d+)\]4\[Size\:\s(.*)\]5\[FL\:\s(no|yes)\]6\[Scene\:\s(no|yes)\](?:7\[Pred\s(.*)\sago\])?' vars: - category diff --git a/internal/indexer/definitions/torrentleech.yaml b/internal/indexer/definitions/torrentleech.yaml index c949f53..66b721c 100644 --- a/internal/indexer/definitions/torrentleech.yaml +++ b/internal/indexer/definitions/torrentleech.yaml @@ -14,28 +14,38 @@ supports: source: custom settings: - name: rsskey - type: text + type: secret label: RSS key - tooltip: The rsskey in your TorrentLeech RSS feed link. - description: "Go to your profile and copy and paste your RSS link to extract the rsskey." + help: "Go to your profile and copy your RSS key" regex: /([\da-fA-F]{20}) irc: network: TorrentLeech.org - server: irc.torrentleech.org:7021 + server: irc.torrentleech.org port: 7021 + tls: true channels: - "#tlannounces" announcers: - _AnnounceBot_ + settings: + - name: nickserv.account + type: text + required: false + label: NickServ Account + help: NickServ account. Make sure to group your user and bot. Eg. user_bot + - name: nickserv.password + type: secret + required: false + label: NickServ Password + help: NickServ password parse: type: single lines: - - - test: - - "New Torrent Announcement: Name:'debian live 10 6 0 amd64 standard iso' uploaded by 'Anonymous' - http://www.tracker01.test/torrent/263302" - - "New Torrent Announcement: Name:'debian live 10 6 0 amd64 standard iso' uploaded by 'Anonymous' freeleech - http://www.tracker01.test/torrent/263302" + - test: + - "New Torrent Announcement: Name:'debian live 10 6 0 amd64 standard iso' uploaded by 'Anonymous' - http://www.tracker01.test/torrent/000000" + - "New Torrent Announcement: Name:'debian live 10 6 0 amd64 standard iso' uploaded by 'Anonymous' freeleech - http://www.tracker01.test/torrent/000000" pattern: New Torrent Announcement:\s*<([^>]*)>\s*Name:'(.*)' uploaded by '([^']*)'\s*(freeleech)*\s*-\s*https?\:\/\/([^\/]+\/)torrent\/(\d+) vars: - category diff --git a/internal/indexer/definitions/uhdbits.yaml b/internal/indexer/definitions/uhdbits.yaml index 9b16add..9345829 100644 --- a/internal/indexer/definitions/uhdbits.yaml +++ b/internal/indexer/definitions/uhdbits.yaml @@ -13,33 +13,48 @@ supports: - rss source: gazelle settings: - - name: authkey - type: text - label: Auth key - tooltip: Right click DL on a torrent and get the authkey. - description: Right click DL on a torrent and get the authkey. - - name: torrent_pass - type: text - label: Torrent pass - tooltip: Right click DL on a torrent and get the torrent_pass. - description: Right click DL on a torrent and get the torrent_pass. + - name: authkey + type: secret + label: Auth key + help: Right click DL on a torrent and get the authkey. + - name: torrent_pass + type: secret + label: Torrent pass + help: Right click DL on a torrent and get the torrent_pass. irc: network: P2P-Network - server: irc.p2p-network.net:6697 + server: irc.p2p-network.net port: 6697 + tls: true channels: - "#UHD.Announce" announcers: - UHDBot - cr0nusbot + settings: + - name: nickserv.account + type: text + required: true + label: NickServ Account + help: NickServ account. Make sure to group your user and bot. Eg. user|autodl + - name: nickserv.password + type: secret + required: true + label: NickServ Password + help: NickServ password + - name: invite_command + type: secret + default: "UHDBot invite IRCKey" + required: true + label: Invite command + help: Invite auth with UHDBot. parse: type: single lines: - - - test: - - "New Torrent: D'Ardennen [2015] - TayTO Type: Movie / 1080p / Encode / Freeleech: 100 Size: 7.00GB - https://uhdbits.org/torrents.php?id=13882 / https://uhdbits.org/torrents.php?action=download&id=20488" + - test: + - "New Torrent: A Movie [2015] - GROUP Type: Movie / 1080p / Encode / Freeleech: 100 Size: 7.00GB - https://uhdbits.org/torrents.php?id=00000 / https://uhdbits.org/torrents.php?action=download&id=00000" pattern: 'New Torrent: (.*) Type: (.*?) Freeleech: (.*) Size: (.*) - https?:\/\/.* \/ (https?:\/\/.*id=\d+)' vars: - torrentName diff --git a/internal/indexer/service.go b/internal/indexer/service.go index 92fc206..d6c5c5b 100644 --- a/internal/indexer/service.go +++ b/internal/indexer/service.go @@ -5,9 +5,8 @@ import ( "io/fs" "strings" - "gopkg.in/yaml.v2" - "github.com/rs/zerolog/log" + "gopkg.in/yaml.v2" "github.com/autobrr/autobrr/internal/domain" ) @@ -26,9 +25,15 @@ type Service interface { } type service struct { - repo domain.IndexerRepo - indexerDefinitions map[string]domain.IndexerDefinition - indexerInstances map[string]domain.IndexerDefinition + repo domain.IndexerRepo + + // contains all raw indexer definitions + indexerDefinitions map[string]domain.IndexerDefinition + + // contains indexers with data set + indexerInstances map[string]domain.IndexerDefinition + + // map server:channel:announce to indexer.Identifier mapIndexerIRCToName map[string]string } @@ -44,6 +49,14 @@ func NewService(repo domain.IndexerRepo) Service { func (s *service) Store(indexer domain.Indexer) (*domain.Indexer, error) { i, err := s.repo.Store(indexer) if err != nil { + log.Error().Stack().Err(err).Msgf("failed to store indexer: %v", indexer.Name) + return nil, err + } + + // add to indexerInstances + err = s.addIndexer(*i) + if err != nil { + log.Error().Stack().Err(err).Msgf("failed to add indexer: %v", indexer.Name) return nil, err } @@ -56,6 +69,13 @@ func (s *service) Update(indexer domain.Indexer) (*domain.Indexer, error) { return nil, err } + // add to indexerInstances + err = s.addIndexer(*i) + if err != nil { + log.Error().Stack().Err(err).Msgf("failed to add indexer: %v", indexer.Name) + return nil, err + } + return i, nil } @@ -94,46 +114,60 @@ func (s *service) GetAll() ([]*domain.IndexerDefinition, error) { var res = make([]*domain.IndexerDefinition, 0) for _, indexer := range indexers { - in := s.getDefinitionByName(indexer.Identifier) - if in == nil { - // if no indexerDefinition found, continue + indexerDefinition, err := s.mapIndexer(indexer) + if err != nil { continue } - temp := domain.IndexerDefinition{ - ID: indexer.ID, - Name: in.Name, - Identifier: in.Identifier, - Enabled: indexer.Enabled, - Description: in.Description, - Language: in.Language, - Privacy: in.Privacy, - Protocol: in.Protocol, - URLS: in.URLS, - Settings: nil, - SettingsMap: make(map[string]string), - IRC: in.IRC, - Parse: in.Parse, + if indexerDefinition == nil { + continue } - // map settings - // add value to settings objects - for _, setting := range in.Settings { - if v, ok := indexer.Settings[setting.Name]; ok { - setting.Value = v - - temp.SettingsMap[setting.Name] = v - } - - temp.Settings = append(temp.Settings, setting) - } - - res = append(res, &temp) + res = append(res, indexerDefinition) } return res, nil } +func (s *service) mapIndexer(indexer domain.Indexer) (*domain.IndexerDefinition, error) { + + in := s.getDefinitionByName(indexer.Identifier) + if in == nil { + // if no indexerDefinition found, continue + return nil, nil + } + + indexerDefinition := domain.IndexerDefinition{ + ID: int(indexer.ID), + Name: in.Name, + Identifier: in.Identifier, + Enabled: indexer.Enabled, + Description: in.Description, + Language: in.Language, + Privacy: in.Privacy, + Protocol: in.Protocol, + URLS: in.URLS, + Settings: nil, + SettingsMap: make(map[string]string), + IRC: in.IRC, + Parse: in.Parse, + } + + // map settings + // add value to settings objects + for _, setting := range in.Settings { + if v, ok := indexer.Settings[setting.Name]; ok { + setting.Value = v + + indexerDefinition.SettingsMap[setting.Name] = v + } + + indexerDefinition.Settings = append(indexerDefinition.Settings, setting) + } + + return &indexerDefinition, nil +} + func (s *service) GetTemplates() ([]domain.IndexerDefinition, error) { definitions := s.indexerDefinitions @@ -152,44 +186,81 @@ func (s *service) Start() error { return err } - indexers, err := s.GetAll() + indexerDefinitions, err := s.GetAll() if err != nil { return err } - for _, indexer := range indexers { - if !indexer.Enabled { - continue - } + for _, indexerDefinition := range indexerDefinitions { + s.indexerInstances[indexerDefinition.Identifier] = *indexerDefinition - s.indexerInstances[indexer.Identifier] = *indexer - - // map irc stuff to indexer.name - if indexer.IRC != nil { - server := indexer.IRC.Server - - for _, channel := range indexer.IRC.Channels { - for _, announcer := range indexer.IRC.Announcers { - val := fmt.Sprintf("%v:%v:%v", server, channel, announcer) - s.mapIndexerIRCToName[val] = indexer.Identifier - } - } - } + s.mapIRCIndexerLookup(indexerDefinition.Identifier, *indexerDefinition) } return nil } +func (s *service) removeIndexer(indexer domain.Indexer) error { + + delete(s.indexerDefinitions, indexer.Identifier) + + // TODO delete from mapIndexerIRCToName + + return nil +} + +func (s *service) addIndexer(indexer domain.Indexer) error { + + // TODO only add if not already there?? Overwrite? + + indexerDefinition, err := s.mapIndexer(indexer) + if err != nil { + return err + } + + // TODO only add enabled? + //if !indexer.Enabled { + // continue + //} + + s.indexerInstances[indexerDefinition.Identifier] = *indexerDefinition + + s.mapIRCIndexerLookup(indexer.Identifier, *indexerDefinition) + + return nil +} + +func (s *service) mapIRCIndexerLookup(indexerIdentifier string, indexerDefinition domain.IndexerDefinition) { + // map irc stuff to indexer.name + // map[irc.network.test:channel:announcer1] = indexer1 + // map[irc.network.test:channel:announcer2] = indexer2 + if indexerDefinition.IRC != nil { + server := indexerDefinition.IRC.Server + channels := indexerDefinition.IRC.Channels + announcers := indexerDefinition.IRC.Announcers + + for _, channel := range channels { + for _, announcer := range announcers { + // format to server:channel:announcer + val := fmt.Sprintf("%v:%v:%v", server, channel, announcer) + val = strings.ToLower(val) + + s.mapIndexerIRCToName[val] = indexerIdentifier + } + } + } +} + // LoadIndexerDefinitions load definitions from golang embed fs func (s *service) LoadIndexerDefinitions() error { entries, err := fs.ReadDir(Definitions, "definitions") if err != nil { - log.Fatal().Msgf("failed reading directory: %s", err) + log.Fatal().Stack().Msgf("failed reading directory: %s", err) } if len(entries) == 0 { - log.Fatal().Msgf("failed reading directory: %s", err) + log.Fatal().Stack().Msgf("failed reading directory: %s", err) return err } @@ -197,19 +268,19 @@ func (s *service) LoadIndexerDefinitions() error { filePath := "definitions/" + f.Name() if strings.Contains(f.Name(), ".yaml") { - log.Debug().Msgf("parsing: %v", filePath) + log.Trace().Msgf("parsing: %v", filePath) var d domain.IndexerDefinition data, err := fs.ReadFile(Definitions, filePath) if err != nil { - log.Debug().Err(err).Msgf("failed reading file: %v", filePath) + log.Error().Stack().Err(err).Msgf("failed reading file: %v", filePath) return err } err = yaml.Unmarshal(data, &d) if err != nil { - log.Error().Err(err).Msgf("failed unmarshal file: %v", filePath) + log.Error().Stack().Err(err).Msgf("failed unmarshal file: %v", filePath) return err } @@ -217,10 +288,13 @@ func (s *service) LoadIndexerDefinitions() error { } } + log.Info().Msgf("Loaded %d indexer definitions", len(s.indexerDefinitions)) + return nil } func (s *service) GetIndexerByAnnounce(name string) *domain.IndexerDefinition { + name = strings.ToLower(name) if identifier, idOk := s.mapIndexerIRCToName[name]; idOk { if indexer, ok := s.indexerInstances[identifier]; ok { diff --git a/internal/irc/handler.go b/internal/irc/handler.go index bd3033d..5c3d24d 100644 --- a/internal/irc/handler.go +++ b/internal/irc/handler.go @@ -25,14 +25,19 @@ type Handler struct { network *domain.IrcNetwork announceService announce.Service + client *irc.Client conn net.Conn ctx context.Context stopped chan struct{} cancel context.CancelFunc + + lastPing time.Time + lastAnnounce time.Time } func NewHandler(network domain.IrcNetwork, announceService announce.Service) *Handler { return &Handler{ + client: nil, conn: nil, ctx: nil, stopped: make(chan struct{}), @@ -44,7 +49,7 @@ func NewHandler(network domain.IrcNetwork, announceService announce.Service) *Ha func (s *Handler) Run() error { //log.Debug().Msgf("server %+v", s.network) - if s.network.Addr == "" { + if s.network.Server == "" { return errors.New("addr not set") } @@ -59,7 +64,7 @@ func (s *Handler) Run() error { var netConn net.Conn var err error - addr := s.network.Addr + addr := fmt.Sprintf("%v:%v", s.network.Server, s.network.Port) // decide to use SSL or not if s.network.TLS { @@ -88,9 +93,9 @@ func (s *Handler) Run() error { log.Info().Msgf("Connected to: %v", addr) config := irc.ClientConfig{ - Nick: s.network.Nick, - User: s.network.Nick, - Name: s.network.Nick, + Nick: s.network.NickServ.Account, + User: s.network.NickServ.Account, + Name: s.network.NickServ.Account, Pass: s.network.Pass, Handler: irc.HandlerFunc(func(c *irc.Client, m *irc.Message) { switch m.Command { @@ -101,38 +106,66 @@ func (s *Handler) Run() error { log.Error().Msgf("error joining channels %v", err) } - case "366": - // TODO: handle joined - log.Debug().Msgf("JOINED: %v", m) + // 322 TOPIC + // 333 UP + // 353 @ + // 396 Displayed host + case "366": // JOINED + s.handleJoined(m) + + case "JOIN": + log.Debug().Msgf("%v: JOIN %v", s.network.Server, m.Trailing()) case "433": // TODO: handle nick in use - log.Debug().Msgf("NICK IN USE: %v", m) + log.Debug().Msgf("%v: NICK IN USE: %v", s.network.Server, m) - case "448", "475", "477": + case "448", "473", "475", "477": // TODO: handle join failed - log.Debug().Msgf("JOIN FAILED: %v", m) + log.Debug().Msgf("%v: JOIN FAILED %v: %v", s.network.Server, m.Params[1], m.Trailing()) + + case "900": // Invite bot logged in + log.Debug().Msgf("%v: %v", s.network.Server, m.Trailing()) case "KICK": - log.Debug().Msgf("KICK: %v", m) + log.Debug().Msgf("%v: KICK: %v", s.network.Server, m) case "MODE": - // TODO: handle mode change - log.Debug().Msgf("MODE CHANGE: %v", m) + err := s.handleMode(m) + if err != nil { + log.Error().Err(err).Msgf("error MODE change: %v", m) + } case "INVITE": // TODO: handle invite - log.Debug().Msgf("INVITE: %v", m) + log.Debug().Msgf("%v: INVITE: %v", s.network.Server, m) case "PART": // TODO: handle parted - log.Debug().Msgf("PART: %v", m) + log.Debug().Msgf("%v: PART: %v", s.network.Server, m) case "PRIVMSG": err := s.onMessage(m) if err != nil { log.Error().Msgf("error on message %v", err) } + + case "CAP": + log.Debug().Msgf("%v: CAP: %v", s.network.Server, m) + + case "NOTICE": + log.Trace().Msgf("%v: %v", s.network.Server, m.Trailing()) + + case "PING": + err := s.handlePing(m) + if err != nil { + log.Error().Stack().Err(err) + } + + //case "372": + // log.Debug().Msgf("372: %v", m) + default: + log.Trace().Msgf("%v: %v", s.network.Server, m.Trailing()) } }), } @@ -140,6 +173,8 @@ func (s *Handler) Run() error { // Create the client client := irc.NewClient(s.conn, config) + s.client = client + // Connect err = client.RunContext(ctx) if err != nil { @@ -157,9 +192,9 @@ func (s *Handler) GetNetwork() *domain.IrcNetwork { func (s *Handler) Stop() { s.cancel() - //if !s.isStopped() { - // close(s.stopped) - //} + if !s.isStopped() { + close(s.stopped) + } if s.conn != nil { s.conn.Close() @@ -176,41 +211,40 @@ func (s *Handler) isStopped() bool { } func (s *Handler) onConnect(client *irc.Client, channels []domain.IrcChannel) error { - // TODO check commands like nickserv before joining + identified := false - for _, command := range s.network.ConnectCommands { - cmd := strings.TrimLeft(command, "/") + time.Sleep(2 * time.Second) - log.Info().Msgf("send connect command: %v to network: %s", cmd, s.network.Name) - - err := client.Write(cmd) + if s.network.NickServ.Password != "" { + err := s.handleNickServPRIVMSG(s.network.NickServ.Account, s.network.NickServ.Password) if err != nil { - log.Error().Err(err).Msgf("error sending connect command %v to network: %v", command, s.network.Name) - continue - //return err + log.Error().Err(err).Msgf("error nickserv: %v", s.network.Name) + return err } - - time.Sleep(1 * time.Second) + identified = true } - for _, ch := range channels { - myChan := fmt.Sprintf("JOIN %s", ch.Name) + time.Sleep(3 * time.Second) - // handle channel password - if ch.Password != "" { - myChan = fmt.Sprintf("JOIN %s %s", ch.Name, ch.Password) - } + if s.network.InviteCommand != "" { - err := client.Write(myChan) + err := s.handleInvitePRIVMSG(s.network.InviteCommand) if err != nil { - log.Error().Err(err).Msgf("error joining channel: %v", ch.Name) - continue - //return err + log.Error().Err(err).Msgf("error sending connect command %v to network: %v", s.network.InviteCommand, s.network.Name) + return err } - log.Info().Msgf("Monitoring channel %s", ch.Name) + time.Sleep(2 * time.Second) + } - time.Sleep(1 * time.Second) + if !identified { + for _, channel := range channels { + err := s.handleJoinChannel(channel.Name) + if err != nil { + log.Error().Err(err) + return err + } + } } return nil @@ -221,7 +255,12 @@ func (s *Handler) OnJoin(msg string) (interface{}, error) { } func (s *Handler) onMessage(msg *irc.Message) error { - log.Debug().Msgf("msg: %v", msg) + //log.Debug().Msgf("raw msg: %v", msg) + + // check if message is from announce bot and correct channel, if not return + //if msg.Name != s.network. { + // + //} // parse announce channel := &msg.Params[0] @@ -231,10 +270,14 @@ func (s *Handler) onMessage(msg *irc.Message) error { // add correlationID and tracing - announceID := fmt.Sprintf("%v:%v:%v", s.network.Addr, *channel, *announcer) + announceID := fmt.Sprintf("%v:%v:%v", s.network.Server, *channel, *announcer) + announceID = strings.ToLower(announceID) // clean message cleanedMsg := cleanMessage(message) + log.Debug().Msgf("%v: %v %v: %v", s.network.Server, *channel, *announcer, cleanedMsg) + + s.lastAnnounce = time.Now() go func() { err := s.announceService.Parse(announceID, cleanedMsg) @@ -246,6 +289,145 @@ func (s *Handler) onMessage(msg *irc.Message) error { return nil } +func (s *Handler) sendPrivMessage(msg string) error { + msg = strings.TrimLeft(msg, "/") + privMsg := fmt.Sprintf("PRIVMSG %s", msg) + + err := s.client.Write(privMsg) + if err != nil { + log.Error().Err(err).Msgf("could not send priv msg: %v", msg) + return err + } + + return nil +} + +func (s *Handler) handleJoinChannel(channel string) error { + m := irc.Message{ + Command: "JOIN", + Params: []string{channel}, + } + + log.Debug().Msgf("%v: %v", s.network.Server, m.String()) + + time.Sleep(1 * time.Second) + + err := s.client.Write(m.String()) + if err != nil { + log.Error().Err(err).Msgf("error handling join: %v", m.String()) + return err + } + + //log.Info().Msgf("Monitoring channel %v %s", s.network.Name, channel) + + return nil +} + +func (s *Handler) handleJoined(msg *irc.Message) { + log.Debug().Msgf("%v: JOINED: %v", s.network.Server, msg.Trailing()) + + log.Info().Msgf("%v: Monitoring channel %s", s.network.Server, msg.Params[1]) +} + +func (s *Handler) handleInvitePRIVMSG(msg string) error { + msg = strings.TrimPrefix(msg, "/msg") + split := strings.Split(msg, " ") + + m := irc.Message{ + Command: "PRIVMSG", + Params: split, + } + + log.Info().Msgf("%v: Invite command: %v", s.network.Server, m.String()) + + err := s.client.Write(m.String()) + if err != nil { + log.Error().Err(err).Msgf("error handling invite: %v", m.String()) + return err + } + + return nil +} + +func (s *Handler) handlePRIVMSG(msg string) error { + msg = strings.TrimLeft(msg, "/") + + m := irc.Message{ + Command: "PRIVMSG", + Params: []string{msg}, + } + log.Debug().Msgf("%v: Handle privmsg: %v", s.network.Server, m.String()) + + err := s.client.Write(m.String()) + if err != nil { + log.Error().Err(err).Msgf("error handling PRIVMSG: %v", m.String()) + return err + } + + return nil +} + +func (s *Handler) handleNickServPRIVMSG(nick, password string) error { + m := irc.Message{ + Command: "PRIVMSG", + Params: []string{"NickServ", "IDENTIFY", nick, password}, + } + + log.Debug().Msgf("%v: NickServ: %v", s.network.Server, m.String()) + + err := s.client.Write(m.String()) + if err != nil { + log.Error().Err(err).Msgf("error identifying with nickserv: %v", m.String()) + return err + } + + return nil +} + +func (s *Handler) handleMode(msg *irc.Message) error { + log.Debug().Msgf("%v: MODE: %v %v", s.network.Server, msg.User, msg.Trailing()) + + time.Sleep(2 * time.Second) + + if s.network.NickServ.Password != "" && !strings.Contains(msg.String(), s.client.CurrentNick()) || !strings.Contains(msg.String(), "+r") { + log.Trace().Msgf("%v: MODE: Not correct permission yet: %v", s.network.Server, msg.String()) + return nil + } + + for _, ch := range s.network.Channels { + err := s.handleJoinChannel(ch.Name) + if err != nil { + log.Error().Err(err).Msgf("error joining channel: %v", ch.Name) + continue + } + + time.Sleep(1 * time.Second) + } + + return nil +} + +func (s *Handler) handlePing(msg *irc.Message) error { + //log.Trace().Msgf("%v: %v", s.network.Server, msg) + + pong := irc.Message{ + Command: "PONG", + Params: msg.Params, + } + + log.Trace().Msgf("%v: %v", s.network.Server, pong.String()) + + err := s.client.Write(pong.String()) + if err != nil { + log.Error().Err(err).Msgf("error PING PONG response: %v", pong.String()) + return err + } + + s.lastPing = time.Now() + + return nil +} + // irc line can contain lots of extra stuff like color so lets clean that func cleanMessage(message string) string { var regexMessageClean = `\x0f|\x1f|\x02|\x03(?:[\d]{1,2}(?:,[\d]{1,2})?)?` diff --git a/internal/irc/service.go b/internal/irc/service.go index 5f82d6b..d0adacf 100644 --- a/internal/irc/service.go +++ b/internal/irc/service.go @@ -13,6 +13,7 @@ import ( type Service interface { StartHandlers() + StopHandlers() StopNetwork(name string) error ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) GetNetworkByID(id int64) (*domain.IrcNetwork, error) @@ -56,7 +57,7 @@ func (s *service) StartHandlers() { s.lock.Lock() channels, err := s.repo.ListChannels(network.ID) if err != nil { - log.Error().Err(err).Msgf("failed to list channels for network %q", network.Addr) + log.Error().Err(err).Msgf("failed to list channels for network %q", network.Server) } network.Channels = channels @@ -79,6 +80,15 @@ func (s *service) StartHandlers() { } } +func (s *service) StopHandlers() { + for _, handler := range s.handlers { + log.Info().Msgf("stopping network: %+v", handler.network.Name) + handler.Stop() + } + + log.Info().Msg("stopped all irc handlers") +} + func (s *service) startNetwork(network domain.IrcNetwork) error { // look if we have the network in handlers already, if so start it if handler, found := s.handlers[network.Name]; found { @@ -134,7 +144,7 @@ func (s *service) GetNetworkByID(id int64) (*domain.IrcNetwork, error) { channels, err := s.repo.ListChannels(network.ID) if err != nil { - log.Error().Err(err).Msgf("failed to list channels for network %q", network.Addr) + log.Error().Err(err).Msgf("failed to list channels for network %q", network.Server) return nil, err } network.Channels = append(network.Channels, channels...) @@ -154,7 +164,7 @@ func (s *service) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) for _, n := range networks { channels, err := s.repo.ListChannels(n.ID) if err != nil { - log.Error().Msgf("failed to list channels for network %q: %v", n.Addr, err) + log.Error().Msgf("failed to list channels for network %q: %v", n.Server, err) return nil, err } n.Channels = append(n.Channels, channels...) @@ -166,11 +176,21 @@ func (s *service) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) } func (s *service) DeleteNetwork(ctx context.Context, id int64) error { - if err := s.repo.DeleteNetwork(ctx, id); err != nil { + network, err := s.GetNetworkByID(id) + if err != nil { return err } - log.Debug().Msgf("delete network: %+v", id) + log.Debug().Msgf("delete network: %v", id) + + // Remove network and handler + if err = s.StopNetwork(network.Name); err != nil { + return err + } + + if err = s.repo.DeleteNetwork(ctx, id); err != nil { + return err + } return nil } @@ -191,24 +211,40 @@ func (s *service) StoreNetwork(network *domain.IrcNetwork) error { } // stop or start network - if !network.Enabled { - log.Debug().Msgf("stopping network: %+v", network.Name) - - err := s.StopNetwork(network.Name) - if err != nil { - log.Error().Err(err).Msgf("could not stop network: %+v", network.Name) - return fmt.Errorf("could not stop network: %v", network.Name) - } - } else { - log.Debug().Msgf("starting network: %+v", network.Name) - + if network.Enabled { err := s.startNetwork(*network) if err != nil { log.Error().Err(err).Msgf("could not start network: %+v", network.Name) return fmt.Errorf("could not start network: %v", network.Name) } + + } else { + err := s.StopNetwork(network.Name) + if err != nil { + log.Error().Err(err).Msgf("could not stop network: %+v", network.Name) + return fmt.Errorf("could not stop network: %v", network.Name) + } } + // stop or start network + //if !network.Enabled { + // log.Debug().Msgf("stopping network: %+v", network.Name) + // + // err := s.StopNetwork(network.Name) + // if err != nil { + // log.Error().Err(err).Msgf("could not stop network: %+v", network.Name) + // return fmt.Errorf("could not stop network: %v", network.Name) + // } + //} else { + // log.Debug().Msgf("starting network: %+v", network.Name) + // + // err := s.startNetwork(*network) + // if err != nil { + // log.Error().Err(err).Msgf("could not start network: %+v", network.Name) + // return fmt.Errorf("could not start network: %v", network.Name) + // } + //} + return nil } diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 2f7a3f9..1697184 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -24,6 +24,8 @@ func Setup(cfg domain.Config) { zerolog.SetGlobalLevel(zerolog.ErrorLevel) case "WARN": zerolog.SetGlobalLevel(zerolog.WarnLevel) + case "TRACE": + zerolog.SetGlobalLevel(zerolog.TraceLevel) default: zerolog.SetGlobalLevel(zerolog.ErrorLevel) } diff --git a/internal/release/process.go b/internal/release/process.go index 5c785fe..6674d4e 100644 --- a/internal/release/process.go +++ b/internal/release/process.go @@ -24,7 +24,7 @@ func NewService(actionService action.Service) Service { } func (s *service) Process(announce domain.Announce) error { - log.Debug().Msgf("start to process release: %+v", announce) + log.Trace().Msgf("start to process release: %+v", announce) if announce.Filter.Actions == nil { return fmt.Errorf("no actions for filter: %v", announce.Filter.Name) diff --git a/internal/server/server.go b/internal/server/server.go index f2d4404..8de1613 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -41,3 +41,10 @@ func (s *Server) Start() error { return nil } + +func (s *Server) Shutdown() { + log.Info().Msg("Shutting down server") + + // stop all irc handlers + s.ircService.StopHandlers() +} diff --git a/web/src/components/inputs/SwitchGroup.tsx b/web/src/components/inputs/SwitchGroup.tsx index eb57421..c01a248 100644 --- a/web/src/components/inputs/SwitchGroup.tsx +++ b/web/src/components/inputs/SwitchGroup.tsx @@ -7,10 +7,11 @@ interface Props { name: string; label: string; description?: string; + defaultValue?: boolean; className?: string; } -const SwitchGroup: React.FC = ({name, label, description}) => ( +const SwitchGroup: React.FC = ({name, label, description, defaultValue}) => (
    @@ -27,6 +28,7 @@ const SwitchGroup: React.FC = ({name, label, description}) => ( ( = ({name, label, placeholder, required, className}) => ( -
    = ({ name, label, help, placeholder, defaultValue, required, hidden, className}) => ( +