Refactor irc client (#19)

* refactor: update http handlers

* feat: add trace log level

* refactir: irc handler

* refactor(definitions): add irc settings and invite cmd:

* feat: add dft values to inputs

* refactor: indexer irc forms

* refactor(definitions): fix nickserv.password var:

* feat: pre fill indexer name field

* refactor: handle stopping and updates
This commit is contained in:
Ludvig Lundgren 2021-08-29 23:23:02 +02:00 committed by GitHub
parent 5f69ae9380
commit 4d40d41628
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 1380 additions and 943 deletions

View file

@ -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
}
}
}

View file

@ -33,7 +33,7 @@ port = 8989
#
# Options: "ERROR", "DEBUG", "INFO", "WARN"
#
logLevel = "DEBUG"
logLevel = "TRACE"
# Session secret
#

View file

@ -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
}

View file

@ -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")

View file

@ -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

View file

@ -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 {

View file

@ -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 {

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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 {
//
}

View file

@ -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 {
//
}

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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: <PC :: Iso> Name:'debian live 10 6 0 amd64 standard iso' uploaded by 'Anonymous' - http://www.tracker01.test/torrent/263302"
- "New Torrent Announcement: <PC :: Iso> Name:'debian live 10 6 0 amd64 standard iso' uploaded by 'Anonymous' freeleech - http://www.tracker01.test/torrent/263302"
- test:
- "New Torrent Announcement: <PC :: Iso> Name:'debian live 10 6 0 amd64 standard iso' uploaded by 'Anonymous' - http://www.tracker01.test/torrent/000000"
- "New Torrent Announcement: <PC :: Iso> 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

View file

@ -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

View file

@ -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 {

View file

@ -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})?)?`

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)

View file

@ -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()
}

View file

@ -7,10 +7,11 @@ interface Props {
name: string;
label: string;
description?: string;
defaultValue?: boolean;
className?: string;
}
const SwitchGroup: React.FC<Props> = ({name, label, description}) => (
const SwitchGroup: React.FC<Props> = ({name, label, description, defaultValue}) => (
<ul className="mt-2 divide-y divide-gray-200">
<Switch.Group as="li" className="py-4 flex items-center justify-between">
<div className="flex flex-col">
@ -27,6 +28,7 @@ const SwitchGroup: React.FC<Props> = ({name, label, description}) => (
<Field
name={name}
defaultValue={defaultValue as any}
render={({input: {onChange, checked, value}}) => (
<Switch
value={value}

View file

@ -1,18 +1,21 @@
import {Field} from "react-final-form";
import { Field } from "react-final-form";
import React from "react";
import Error from "./Error";
import {classNames} from "../../styles/utils";
import { classNames } from "../../styles/utils";
interface Props {
name: string;
label?: string;
help?: string;
placeholder?: string;
defaultValue?: string;
className?: string;
required?: boolean;
hidden?: boolean;
}
const TextFieldWide: React.FC<Props> = ({name, label, placeholder, required, className}) => (
<div
const TextFieldWide: React.FC<Props> = ({ name, label, help, placeholder, defaultValue, required, hidden, className}) => (
<div hidden={hidden}
className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div>
@ -23,17 +26,22 @@ const TextFieldWide: React.FC<Props> = ({name, label, placeholder, required, cla
<div className="sm:col-span-2">
<Field
name={name}
render={({input, meta}) => (
defaultValue={defaultValue}
render={({ input, meta }) => (
<input
{...input}
id={name}
type="text"
className={classNames(meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 focus:border-indigo-500 border-gray-300", "block w-full shadow-sm sm:text-sm rounded-md")}
placeholder={placeholder}
hidden={hidden}
/>
)}
/>
<Error name={name} classNames="block text-red-500 mt-2"/>
{help && (
<p className="mt-2 text-sm text-gray-500" id="email-description">{help}</p>
)}
<Error name={name} classNames="block text-red-500 mt-2" />
</div>
</div>
)

View file

@ -7,15 +7,19 @@ interface Props {
name: string;
label?: string;
placeholder?: string;
defaultValue?: number;
className?: string;
required?: boolean;
hidden?: boolean;
}
const NumberFieldWide: React.FC<Props> = ({
name,
label,
placeholder,
defaultValue,
required,
hidden,
className,
}) => (
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
@ -30,6 +34,7 @@ const NumberFieldWide: React.FC<Props> = ({
<div className="sm:col-span-2">
<Field
name={name}
defaultValue={defaultValue}
parse={(v) => v & parseInt(v, 10)}
render={({ input, meta }) => (
<input

View file

@ -0,0 +1,56 @@
import { Field } from "react-final-form";
import Error from "../Error";
import { classNames } from "../../../styles/utils";
import { useToggle } from "../../../hooks/hooks";
import { EyeIcon, EyeOffIcon } from "@heroicons/react/solid";
interface Props {
name: string;
label?: string;
placeholder?: string;
defaultValue?: string;
help?: string;
required?: boolean;
}
function PasswordField({ name, label, placeholder, defaultValue, help, required }: Props) {
const [isVisible, toggleVisibility] = useToggle(false)
return (
<div
className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div>
<label htmlFor={name} className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2">
{label} {required && <span className="text-gray-500">*</span>}
</label>
</div>
<div className="sm:col-span-2">
<Field
name={name}
defaultValue={defaultValue}
render={({ input, meta }) => (
<div className="relative">
<input
{...input}
id={name}
type={isVisible ? "text" : "password"}
className={classNames(meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 focus:border-indigo-500 border-gray-300", "block w-full shadow-sm sm:text-sm rounded-md")}
placeholder={placeholder}
/>
<div className="absolute inset-y-0 right-0 px-3 flex items-center" onClick={toggleVisibility}>
{!isVisible ? <EyeIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" /> : <EyeOffIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" />}
</div>
</div>
)}
/>
{help && (
<p className="mt-2 text-sm text-gray-500" id="email-description">{help}</p>
)}
<Error name={name} classNames="block text-red-500 mt-2" />
</div>
</div>
)
}
export default PasswordField;

View file

@ -1,3 +1,4 @@
export { default as NumberFieldWide } from "./NumberField";
export { default as PasswordFieldWide } from "./PasswordField";
export { default as RadioFieldsetWide } from "./RadioFieldsetWide";
export { default as SelectFieldWide } from "./SelectField";

View file

@ -31,6 +31,43 @@ export interface Indexer {
settings: object | any;
}
export interface IndexerSchema {
// id: number;
name: string;
identifier: string;
description: string;
language: string;
privacy: string;
protocol: string;
urls: string[];
settings: IndexerSchemaSettings[];
irc: IndexerSchemaIRC;
}
export interface IndexerSchemaSettings {
name: string;
type: string;
required: boolean;
label: string;
help: string;
description: string;
default: string;
}
export interface IndexerSchemaIRC {
network: string;
server: string;
port: number;
tls: boolean;
nickserv: boolean;
announcers: string[];
channels: string[];
invite: string[];
invite_command: string;
settings: IndexerSchemaSettings[];
}
export interface Filter {
id: number;
name: string;
@ -87,16 +124,29 @@ export interface DownloadClient {
settings: object;
}
export interface NickServ {
account: string;
password: string;
}
export interface Network {
id: number;
id?: number;
name: string;
enabled: boolean;
addr: string;
nick: string;
username: string;
realname: string;
pass: string;
sasl: SASL;
server: string;
port: number;
tls: boolean;
invite_command: string;
nickserv: {
account: string;
password: string;
}
channels: Channel[];
settings: object;
}
export interface Channel {
name: string;
}
export interface SASL {

View file

@ -1,23 +1,24 @@
import React, {Fragment} from "react";
import {useMutation, useQuery} from "react-query";
import {Indexer} from "../../domain/interfaces";
import {sleep} from "../../utils/utils";
import {XIcon} from "@heroicons/react/solid";
import {Dialog, Transition} from "@headlessui/react";
import {Field, Form} from "react-final-form";
import React, { Fragment } from "react";
import { useMutation, useQuery } from "react-query";
import { Channel, Indexer, IndexerSchema, IndexerSchemaSettings, Network } from "../../domain/interfaces";
import { sleep } from "../../utils/utils";
import { XIcon } from "@heroicons/react/solid";
import { Dialog, Transition } from "@headlessui/react";
import { Field, Form } from "react-final-form";
import DEBUG from "../../components/debug";
import Select from "react-select";
import {queryClient} from "../../App";
import { SwitchGroup } from "../../components/inputs";
import { queryClient } from "../../App";
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
import APIClient from "../../api/APIClient";
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
interface props {
isOpen: boolean;
toggle: any;
}
function IndexerAddForm({isOpen, toggle}: props) {
const {data} = useQuery<any[], Error>('indexerSchema', APIClient.indexers.getSchema,
function IndexerAddForm({ isOpen, toggle }: props) {
const { data } = useQuery<IndexerSchema[], Error>('indexerSchema', APIClient.indexers.getSchema,
{
enabled: isOpen,
refetchOnWindowFocus: false
@ -33,59 +34,122 @@ function IndexerAddForm({isOpen, toggle}: props) {
}
})
const onSubmit = (data: any) => {
mutation.mutate(data)
const ircMutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
onSuccess: (data) => {
console.log("irc mutation: ", data);
// queryClient.invalidateQueries(['indexer']);
// sleep(1500)
// toggle()
}
})
const onSubmit = (formData: any) => {
let ind = data && data.find(i => i.identifier === formData.identifier)
if (!ind) {
return
}
let channels: Channel[] = []
if (ind.irc.channels.length) {
ind.irc.channels.forEach(element => {
channels.push({ name: element })
});
}
const network: Network = {
name: ind.name,
enabled: false,
server: formData.irc.server,
port: formData.irc.port,
tls: formData.irc.tls,
nickserv: formData.irc.nickserv,
invite_command: formData.irc.invite_command,
settings: formData.irc.settings,
channels: channels,
}
console.log("network: ", network);
mutation.mutate(formData, {
onSuccess: (data) => {
// create irc
ircMutation.mutate(network)
}
})
};
const renderSettingFields = (indexer: string) => {
if (indexer !== "") {
// let ind = data.find(i => i.implementation_name === indexer)
let ind = data && data.find(i => i.identifier === indexer)
return (
<div key="opt">
{ind && ind.settings && ind.settings.map((f: any, idx: number) => {
switch (f.type) {
case "text":
return (
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5" key={idx}>
<div>
<label
htmlFor={f.name}
className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2"
>
{f.label}
</label>
</div>
<div className="sm:col-span-2">
<Field name={"settings."+f.name}>
{({input, meta}) => (
<div className="sm:col-span-2">
<input
type="text"
{...input}
className="block w-full shadow-sm sm:text-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 rounded-md"
/>
{meta.touched && meta.error &&
<span>{meta.error}</span>}
</div>
)}
</Field>
</div>
</div>
)
}
})}
{ind && ind.settings && ind.settings.map((f: any, idx: number) => {
switch (f.type) {
case "text":
return (
<TextFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} defaultValue=""/>
)
case "secret":
return (
<PasswordFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} defaultValue="" />
)
}
})}
<div hidden={true}>
<TextFieldWide name={`name`} label="Name" defaultValue={ind?.name} />
</div>
</div>
)
}
}
const renderIrcSettingFields = (indexer: string) => {
if (indexer !== "") {
let ind = data && data.find(i => i.identifier === indexer)
return (
<Fragment>
{ind && ind.irc && ind.irc.settings && (
<div className="border-t border-gray-200 py-5">
<div className="px-6 space-y-1">
<Dialog.Title className="text-lg font-medium text-gray-900">IRC</Dialog.Title>
<p className="text-sm text-gray-500">
Networks, channels and invite commands are configured automatically.
</p>
</div>
{ind.irc.settings.map((f: IndexerSchemaSettings, idx: number) => {
switch (f.type) {
case "text":
return <TextFieldWide name={`irc.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} />
case "secret":
return <PasswordFieldWide name={`irc.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} defaultValue={f.default} />
}
})}
<div hidden={true}>
<TextFieldWide name={`irc.server`} label="Server" defaultValue={ind.irc.server} />
<NumberFieldWide name={`irc.port`} label="Port" defaultValue={ind.irc.port} />
<SwitchGroup name="irc.tls" label="TLS" defaultValue={ind.irc.tls} />
</div>
</div>
)}
</Fragment>
)
}
}
return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" static className="fixed inset-0 overflow-hidden" open={isOpen} onClose={toggle}>
<div className="absolute inset-0 overflow-hidden">
<Dialog.Overlay className="absolute inset-0"/>
<Dialog.Overlay className="absolute inset-0" />
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
<Transition.Child
@ -100,18 +164,17 @@ function IndexerAddForm({isOpen, toggle}: props) {
<div className="w-screen max-w-2xl">
<Form
initialValues={{
name: "",
enabled: true,
identifier: "",
irc: {}
}}
onSubmit={onSubmit}
>
{({handleSubmit, values}) => {
{({ handleSubmit, values }) => {
return (
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
onSubmit={handleSubmit}>
onSubmit={handleSubmit}>
<div className="flex-1">
{/* Header */}
<div className="px-4 py-6 bg-gray-50 sm:px-6">
<div className="flex items-start justify-between space-x-3">
<div className="space-y-1">
@ -129,43 +192,14 @@ function IndexerAddForm({isOpen, toggle}: props) {
onClick={toggle}
>
<span className="sr-only">Close panel</span>
<XIcon className="h-6 w-6" aria-hidden="true"/>
<XIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>
</div>
{/* Divider container */}
<div
className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<div
className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div>
<label
htmlFor="name"
className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2"
>
Name
</label>
</div>
<Field name="name">
{({input, meta}) => (
<div className="sm:col-span-2">
<input
type="text"
{...input}
className="block w-full shadow-sm sm:text-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 rounded-md"
/>
{meta.touched && meta.error &&
<span>{meta.error}</span>}
</div>
)}
</Field>
</div>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="enabled" label="Enabled" />
</div>
<div
className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
@ -182,26 +216,32 @@ function IndexerAddForm({isOpen, toggle}: props) {
name="identifier"
parse={val => val && val.value}
format={val => data && data.find((o: any) => o.value === val)}
render={({input, meta}) => (
render={({ input, meta }) => (
<React.Fragment>
<Select {...input}
isClearable={true}
placeholder="Choose an indexer"
options={data && data.sort((a,b): any => a.name.localeCompare(b.name)).map(v => ({
label: v.name,
value: v.identifier
// value: v.implementation_name
}))}/>
{/*<Error name={input.name} classNames="text-red mt-2 block" />*/}
isClearable={true}
placeholder="Choose an indexer"
options={data && data.sort((a, b): any => a.name.localeCompare(b.name)).map(v => ({
label: v.name,
value: v.identifier
}))} />
</React.Fragment>
)}
/>
</div>
</div>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="enabled" label="Enabled" />
</div>
{renderSettingFields(values.identifier)}
</div>
{renderIrcSettingFields(values.identifier)}
</div>
<div
@ -223,7 +263,7 @@ function IndexerAddForm({isOpen, toggle}: props) {
</div>
</div>
<DEBUG values={values}/>
<DEBUG values={values} />
</form>
)
}}

View file

@ -1,15 +1,16 @@
import {Fragment, useRef} from "react";
import {useMutation } from "react-query";
import {Indexer} from "../../domain/interfaces";
import {sleep} from "../../utils/utils";
import {ExclamationIcon, XIcon} from "@heroicons/react/solid";
import {Dialog, Transition} from "@headlessui/react";
import {Field, Form} from "react-final-form";
import { Fragment, useRef } from "react";
import { useMutation } from "react-query";
import { Indexer } from "../../domain/interfaces";
import { sleep } from "../../utils/utils";
import { ExclamationIcon, XIcon } from "@heroicons/react/solid";
import { Dialog, Transition } from "@headlessui/react";
import { Field, Form } from "react-final-form";
import DEBUG from "../../components/debug";
import { SwitchGroup } from "../../components/inputs";
import {useToggle} from "../../hooks/hooks";
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
import { useToggle } from "../../hooks/hooks";
import APIClient from "../../api/APIClient";
import {queryClient} from "../../App";
import { queryClient } from "../../App";
import { PasswordFieldWide } from "../../components/inputs/wide";
interface props {
isOpen: boolean;
@ -17,7 +18,7 @@ interface props {
indexer: Indexer;
}
function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
function IndexerUpdateForm({ isOpen, toggle, indexer }: props) {
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false)
const mutation = useMutation((indexer: Indexer) => APIClient.indexers.update(indexer), {
@ -55,31 +56,11 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
switch (f.type) {
case "text":
return (
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5" key={idx}>
<div>
<label
htmlFor={f.name}
className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2"
>
{f.label}
</label>
</div>
<div className="sm:col-span-2">
<Field name={"settings."+f.name}>
{({input, meta}) => (
<div className="sm:col-span-2">
<input
type="text"
{...input}
className="block w-full shadow-sm sm:text-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 rounded-md"
/>
{meta.touched && meta.error &&
<span>{meta.error}</span>}
</div>
)}
</Field>
</div>
</div>
<TextFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} />
)
case "secret":
return (
<PasswordFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} />
)
}
})}
@ -88,9 +69,6 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
}
}
// const setss = indexer.settings.reduce((o: any, obj: any) => ({ ...o, [obj.name]: obj.value }), {})
// console.log("setts", setss)
return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" static className="fixed inset-0 overflow-hidden" open={isOpen} onClose={toggle}>
@ -119,8 +97,8 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
{/* This element is to trick the browser into centering the modal contents. */}
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
</span>
&#8203;
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
@ -172,7 +150,7 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
</Dialog>
</Transition.Root>
<div className="absolute inset-0 overflow-hidden">
<Dialog.Overlay className="absolute inset-0"/>
<Dialog.Overlay className="absolute inset-0" />
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
<Transition.Child
@ -195,12 +173,11 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
}}
onSubmit={onSubmit}
>
{({handleSubmit, values}) => {
{({ handleSubmit, values }) => {
return (
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
onSubmit={handleSubmit}>
onSubmit={handleSubmit}>
<div className="flex-1">
{/* Header */}
<div className="px-4 py-6 bg-gray-50 sm:px-6">
<div className="flex items-start justify-between space-x-3">
<div className="space-y-1">
@ -218,13 +195,12 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
onClick={toggle}
>
<span className="sr-only">Close panel</span>
<XIcon className="h-6 w-6" aria-hidden="true"/>
<XIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>
</div>
{/* Divider container */}
<div
className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<div
@ -238,7 +214,7 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
</label>
</div>
<Field name="name">
{({input, meta}) => (
{({ input, meta }) => (
<div className="sm:col-span-2">
<input
type="text"
@ -246,7 +222,7 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
className="block w-full shadow-sm sm:text-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 rounded-md"
/>
{meta.touched && meta.error &&
<span>{meta.error}</span>}
<span>{meta.error}</span>}
</div>
)}
</Field>
@ -289,7 +265,7 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
</div>
</div>
<DEBUG values={values}/>
<DEBUG values={values} />
</form>
)
}}

View file

@ -5,26 +5,14 @@ import {Dialog, Transition} from "@headlessui/react";
import {XIcon} from "@heroicons/react/solid";
import {Field, Form} from "react-final-form";
import DEBUG from "../../components/debug";
import {SwitchGroup, TextAreaWide, TextFieldWide} from "../../components/inputs";
import {SwitchGroup, TextFieldWide} from "../../components/inputs";
import {queryClient} from "../../App";
import arrayMutators from "final-form-arrays";
import { FieldArray } from "react-final-form-arrays";
import {classNames} from "../../styles/utils";
import APIClient from "../../api/APIClient";
// interface radioFieldsetOption {
// label: string;
// description: string;
// value: string;
// }
// const saslTypeOptions: radioFieldsetOption[] = [
// {label: "None", description: "None", value: ""},
// {label: "Plain", description: "SASL plain", value: "PLAIN"},
// {label: "NickServ", description: "/NS identify", value: "NICKSERV"},
// ];
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
function IrcNetworkAddForm({isOpen, toggle}: any) {
const mutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
@ -53,12 +41,8 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
errors.name = "Required";
}
if (!values.addr) {
errors.addr = "Required";
}
if (!values.nick) {
errors.nick = "Required";
if (!values.server) {
errors.server = "Required";
}
return errors;
@ -86,18 +70,9 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
initialValues={{
name: "",
enabled: true,
addr: "",
server: "",
tls: false,
nick: "",
pass: "",
// connect_commands: "",
// sasl: {
// mechanism: "",
// plain: {
// username: "",
// password: "",
// }
// },
}}
mutators={{
...arrayMutators
@ -110,7 +85,6 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
onSubmit={handleSubmit}>
<div className="flex-1">
{/* Header */}
<div className="px-4 py-6 bg-gray-50 sm:px-6">
<div className="flex items-start justify-between space-x-3">
<div className="space-y-1">
@ -144,88 +118,19 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
</div>
<div>
<TextFieldWide name="addr" label="Address" placeholder="Address:port eg irc.server.net:6697" required={true} />
<TextFieldWide name="server" label="Server" placeholder="Address: Eg irc.server.net" required={true} />
<NumberFieldWide name="port" label="Port" required={true} />
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="tls" label="TLS"/>
</div>
<TextFieldWide name="nick" label="Nick" placeholder="Nick" required={true} />
<PasswordFieldWide name="pass" label="Password" help="Network password" />
<TextFieldWide name="password" label="Password" placeholder="Network password" />
<TextFieldWide name="nickserv.account" label="NickServ Account" required={true} />
<PasswordFieldWide name="nickserv.password" label="NickServ Password" />
<TextAreaWide name="connect_commands" label="Connect commands" placeholder="/msg test this" />
{/* <Field*/}
{/* name="sasl.mechanism"*/}
{/* type="select"*/}
{/* render={({input}) => (*/}
{/* <Listbox value={input.value} onChange={input.onChange}>*/}
{/* {({open}) => (*/}
{/* <div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">*/}
{/* <div>*/}
{/* <Listbox.Label className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2">SASL / auth</Listbox.Label>*/}
{/* </div>*/}
{/* <div className="sm:col-span-2 relative">*/}
{/* <Listbox.Button*/}
{/* className="bg-white relative w-full border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">*/}
{/* <span className="block truncate">{input.value ? saslTypeOptions.find(c => c.value === input.value)!.label : "Choose auth method"}</span>*/}
{/* <span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">*/}
{/* <SelectorIcon className="h-5 w-5 text-gray-400" aria-hidden="true"/>*/}
{/*</span>*/}
{/* </Listbox.Button>*/}
{/* <Transition*/}
{/* show={open}*/}
{/* as={Fragment}*/}
{/* leave="transition ease-in duration-100"*/}
{/* leaveFrom="opacity-100"*/}
{/* leaveTo="opacity-0"*/}
{/* >*/}
{/* <Listbox.Options*/}
{/* static*/}
{/* className="absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"*/}
{/* >*/}
{/* {saslTypeOptions.map((opt: any) => (*/}
{/* <Listbox.Option*/}
{/* key={opt.value}*/}
{/* className={({active}) =>*/}
{/* classNames(*/}
{/* active ? 'text-white bg-indigo-600' : 'text-gray-900',*/}
{/* 'cursor-default select-none relative py-2 pl-3 pr-9'*/}
{/* )*/}
{/* }*/}
{/* value={opt.value}*/}
{/* >*/}
{/* {({selected, active}) => (*/}
{/* <>*/}
{/* <span className={classNames(selected ? 'font-semibold' : 'font-normal', 'block truncate')}>*/}
{/* {opt.label}*/}
{/* </span>*/}
{/* {selected ? (*/}
{/* <span*/}
{/* className={classNames(*/}
{/* active ? 'text-white' : 'text-indigo-600',*/}
{/* 'absolute inset-y-0 right-0 flex items-center pr-4'*/}
{/* )}*/}
{/* >*/}
{/* <CheckIcon className="h-5 w-5" aria-hidden="true"/>*/}
{/* </span>*/}
{/* ) : null}*/}
{/* </>*/}
{/* )}*/}
{/* </Listbox.Option>*/}
{/* ))}*/}
{/* </Listbox.Options>*/}
{/* </Transition>*/}
{/* </div>*/}
{/* </div>*/}
{/* )}*/}
{/* </Listbox>*/}
{/* )} />*/}
<PasswordFieldWide name="invite_command" label="Invite command" />
</div>
</div>
@ -295,7 +200,6 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
<button
type="submit"
disabled={pristine || invalid}
// className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
className={classNames(pristine || invalid ? "bg-indigo-300" : "bg-indigo-600 hover:bg-indigo-700","inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500")}
>
Create

View file

@ -1,34 +1,22 @@
import {Fragment, useEffect, useRef} from "react";
import {useMutation} from "react-query";
import {Network} from "../../domain/interfaces";
import {Dialog, Transition} from "@headlessui/react";
import {XIcon} from "@heroicons/react/solid";
import {Field, Form} from "react-final-form";
import { Fragment, useEffect, useRef } from "react";
import { useMutation } from "react-query";
import { Network } from "../../domain/interfaces";
import { Dialog, Transition } from "@headlessui/react";
import { XIcon } from "@heroicons/react/solid";
import { Field, Form } from "react-final-form";
import DEBUG from "../../components/debug";
import {SwitchGroup, TextAreaWide, TextFieldWide} from "../../components/inputs";
import {queryClient} from "../../App";
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
import { queryClient } from "../../App";
import arrayMutators from "final-form-arrays";
import { FieldArray } from "react-final-form-arrays";
import {classNames} from "../../styles/utils";
import {useToggle} from "../../hooks/hooks";
import {DeleteModal} from "../../components/modals";
import { classNames } from "../../styles/utils";
import { useToggle } from "../../hooks/hooks";
import { DeleteModal } from "../../components/modals";
import APIClient from "../../api/APIClient";
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
// interface radioFieldsetOption {
// label: string;
// description: string;
// value: string;
// }
//
// const saslTypeOptions: radioFieldsetOption[] = [
// {label: "None", description: "None", value: ""},
// {label: "Plain", description: "SASL plain", value: "PLAIN"},
// {label: "NickServ", description: "/NS identify", value: "NICKSERV"},
// ];
function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false)
const mutation = useMutation((network: Network) => APIClient.irc.updateNetwork(network), {
@ -59,7 +47,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
// let cmds = data.connect_commands && data.connect_commands.length > 0 ? data.connect_commands.replace(/\r\n/g,"\n").split("\n") : [];
// data.connect_commands = cmds
// console.log("formatted", data)
mutation.mutate(data)
};
@ -70,12 +58,16 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
errors.name = "Required";
}
if (!values.addr) {
errors.addr = "Required";
if (!values.server) {
errors.server = "Required";
}
if (!values.nick) {
errors.nick = "Required";
if (!values.port) {
errors.port = "Required";
}
if (!values.nickserv.account) {
errors.nickserv.account = "Required";
}
return errors;
@ -98,7 +90,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
text="Are you sure you want to remove this network and channels? This action cannot be undone."
/>
<div className="absolute inset-0 overflow-hidden">
<Dialog.Overlay className="absolute inset-0"/>
<Dialog.Overlay className="absolute inset-0" />
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
<Transition.Child
@ -117,19 +109,13 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
id: network.id,
name: network.name,
enabled: network.enabled,
addr: network.addr,
server: network.server,
port: network.port,
tls: network.tls,
nick: network.nick,
nickserv: network.nickserv,
pass: network.pass,
invite_command: network.invite_command,
connect_commands: network.connect_commands,
sasl: network.sasl,
// sasl: {
// mechanism: network.sasl.mechanism,
// plain: {
// username: network.sasl.plain.username,
// password: network.sasl.plain.password,
// }
// },
channels: network.channels
}}
mutators={{
@ -138,10 +124,10 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
validate={validate}
onSubmit={onSubmit}
>
{({handleSubmit, values, pristine, invalid}) => {
{({ handleSubmit, values, pristine, invalid }) => {
return (
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
onSubmit={handleSubmit}>
onSubmit={handleSubmit}>
<div className="flex-1">
{/* Header */}
<div className="px-4 py-6 bg-gray-50 sm:px-6">
@ -160,106 +146,48 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
onClick={toggle}
>
<span className="sr-only">Close panel</span>
<XIcon className="h-6 w-6" aria-hidden="true"/>
<XIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>
</div>
<TextFieldWide name="name" label="Name" placeholder="Name" required={true} />
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<div
className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="enabled" label="Enabled"/>
<SwitchGroup name="enabled" label="Enabled" />
</div>
<div>
<TextFieldWide name="addr" label="Address" placeholder="Address:port eg irc.server.net:6697" required={true} />
<div className="px-6 space-y-1 mt-6">
<Dialog.Title className="text-lg font-medium text-gray-900">Connection</Dialog.Title>
{/* <p className="text-sm text-gray-500">
Networks, channels and invite commands are configured automatically.
</p> */}
</div>
<TextFieldWide name="server" label="Server" placeholder="Address: Eg irc.server.net" required={true} />
<NumberFieldWide name="port" label="Port" />
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="tls" label="TLS"/>
<SwitchGroup name="tls" label="TLS" />
</div>
<TextFieldWide name="nick" label="Nick" placeholder="Nick" required={true} />
<PasswordFieldWide name="pass" label="Password" help="Network password" />
<TextFieldWide name="password" label="Password" placeholder="Network password" />
<div className="px-6 space-y-1 border-t pt-6">
<Dialog.Title className="text-lg font-medium text-gray-900">Account</Dialog.Title>
{/* <p className="text-sm text-gray-500">
Networks, channels and invite commands are configured automatically.
</p> */}
</div>
<TextAreaWide name="connect_commands" label="Connect commands" placeholder="/msg test this" />
<TextFieldWide name="nickserv.account" label="NickServ Account" required={true} />
<PasswordFieldWide name="nickserv.password" label="NickServ Password" />
{/* <Field*/}
{/* name="sasl.mechanism"*/}
{/* type="select"*/}
{/* render={({input}) => (*/}
{/* <Listbox value={input.value} onChange={input.onChange}>*/}
{/* {({open}) => (*/}
{/* <div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">*/}
{/* <div>*/}
{/* <Listbox.Label className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2">SASL / auth</Listbox.Label>*/}
{/* </div>*/}
{/* <div className="sm:col-span-2 relative">*/}
{/* <Listbox.Button*/}
{/* className="bg-white relative w-full border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">*/}
{/* <span className="block truncate">{input.value ? saslTypeOptions.find(c => c.value === input.value)!.label : "Choose a auth type"}</span>*/}
{/* /!*<span className="block truncate">Choose a auth type</span>*!/*/}
{/* <span*/}
{/* className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">*/}
{/* <SelectorIcon className="h-5 w-5 text-gray-400" aria-hidden="true"/>*/}
{/*</span>*/}
{/* </Listbox.Button>*/}
{/* <Transition*/}
{/* show={open}*/}
{/* as={Fragment}*/}
{/* leave="transition ease-in duration-100"*/}
{/* leaveFrom="opacity-100"*/}
{/* leaveTo="opacity-0"*/}
{/* >*/}
{/* <Listbox.Options*/}
{/* static*/}
{/* className="absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"*/}
{/* >*/}
{/* {saslTypeOptions.map((opt: any) => (*/}
{/* <Listbox.Option*/}
{/* key={opt.value}*/}
{/* className={({active}) =>*/}
{/* classNames(*/}
{/* active ? 'text-white bg-indigo-600' : 'text-gray-900',*/}
{/* 'cursor-default select-none relative py-2 pl-3 pr-9'*/}
{/* )*/}
{/* }*/}
{/* value={opt.value}*/}
{/* >*/}
{/* {({selected, active}) => (*/}
{/* <>*/}
{/* <span className={classNames(selected ? 'font-semibold' : 'font-normal', 'block truncate')}>*/}
{/* {opt.label}*/}
{/* </span>*/}
{/* {selected ? (*/}
{/* <span*/}
{/* className={classNames(*/}
{/* active ? 'text-white' : 'text-indigo-600',*/}
{/* 'absolute inset-y-0 right-0 flex items-center pr-4'*/}
{/* )}*/}
{/* >*/}
{/* <CheckIcon className="h-5 w-5" aria-hidden="true"/>*/}
{/* </span>*/}
{/* ) : null}*/}
{/* </>*/}
{/* )}*/}
{/* </Listbox.Option>*/}
{/* ))}*/}
{/* </Listbox.Options>*/}
{/* </Transition>*/}
{/* </div>*/}
{/* </div>*/}
{/* )}*/}
{/* </Listbox>*/}
{/* )} />*/}
<PasswordFieldWide name="invite_command" label="Invite command" />
</div>
</div>
@ -294,7 +222,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
onClick={() => fields.remove(index)}
>
<span className="sr-only">Remove</span>
<XIcon className="h-6 w-6" aria-hidden="true"/>
<XIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
))
@ -337,7 +265,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
<button
type="submit"
disabled={pristine || invalid}
className={classNames(pristine || invalid ? "bg-indigo-300" : "bg-indigo-600 hover:bg-indigo-700","inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500")}
className={classNames(pristine || invalid ? "bg-indigo-300" : "bg-indigo-600 hover:bg-indigo-700", "inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500")}
>
Save
</button>
@ -345,27 +273,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
</div>
</div>
{/*<div*/}
{/* className="flex-shrink-0 px-4 border-t border-gray-200 py-5 sm:px-6">*/}
{/* <div className="space-x-3 flex justify-end">*/}
{/* <button*/}
{/* type="button"*/}
{/* className="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"*/}
{/* onClick={toggle}*/}
{/* >*/}
{/* Cancel*/}
{/* </button>*/}
{/* <button*/}
{/* type="submit"*/}
{/* disabled={pristine || invalid}*/}
{/* className={classNames(pristine || invalid ? "bg-indigo-300" : "bg-indigo-600 hover:bg-indigo-700","inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500")}*/}
{/* >*/}
{/* Save*/}
{/* </button>*/}
{/* </div>*/}
{/*</div>*/}
<DEBUG values={values}/>
<DEBUG values={values} />
</form>
)
}}

View file

@ -82,7 +82,7 @@ function IrcSettings() {
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Addr
Server
</th>
<th
scope="col"
@ -138,8 +138,8 @@ const ListItem = ({ idx, network }: any) => {
</Switch>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{network.name}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{network.addr} {network.tls && <span className="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">TLS</span>}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{network.nick}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{network.server}:{network.port} {network.tls && <span className="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">TLS</span>}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{network.nickserv?.account}</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<span className="text-indigo-600 hover:text-indigo-900 cursor-pointer" onClick={toggleUpdate}>
Edit