mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
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:
parent
5f69ae9380
commit
4d40d41628
48 changed files with 1380 additions and 943 deletions
|
@ -94,22 +94,25 @@ func main() {
|
||||||
srv.Port = cfg.Port
|
srv.Port = cfg.Port
|
||||||
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
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 {
|
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 {
|
for sig := range sigCh {
|
||||||
switch sig {
|
switch sig {
|
||||||
case syscall.SIGHUP:
|
case syscall.SIGHUP:
|
||||||
log.Print("shutting down server")
|
log.Print("shutting down server sighup")
|
||||||
|
srv.Shutdown()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
case syscall.SIGINT, syscall.SIGTERM:
|
case syscall.SIGINT, syscall.SIGQUIT:
|
||||||
log.Print("shutting down server")
|
srv.Shutdown()
|
||||||
//srv.Shutdown()
|
os.Exit(1)
|
||||||
|
case syscall.SIGKILL, syscall.SIGTERM:
|
||||||
|
srv.Shutdown()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ port = 8989
|
||||||
#
|
#
|
||||||
# Options: "ERROR", "DEBUG", "INFO", "WARN"
|
# Options: "ERROR", "DEBUG", "INFO", "WARN"
|
||||||
#
|
#
|
||||||
logLevel = "DEBUG"
|
logLevel = "TRACE"
|
||||||
|
|
||||||
# Session secret
|
# Session secret
|
||||||
#
|
#
|
||||||
|
|
|
@ -26,12 +26,15 @@ func (r *IndexerRepo) Store(indexer domain.Indexer) (*domain.Indexer, error) {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
log.Error().Stack().Err(err).Msg("error executing query")
|
log.Error().Stack().Err(err).Msg("error executing query")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
id, _ := res.LastInsertId()
|
||||||
|
indexer.ID = id
|
||||||
|
|
||||||
return &indexer, nil
|
return &indexer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ package database
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/autobrr/autobrr/internal/domain"
|
"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) {
|
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 {
|
if err := row.Err(); err != nil {
|
||||||
log.Fatal().Err(err)
|
log.Fatal().Err(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -32,22 +31,19 @@ func (ir *IrcRepo) GetNetworkByID(id int64) (*domain.IrcNetwork, error) {
|
||||||
|
|
||||||
var n domain.IrcNetwork
|
var n domain.IrcNetwork
|
||||||
|
|
||||||
var pass, connectCommands sql.NullString
|
var pass, inviteCmd sql.NullString
|
||||||
var saslMechanism, saslPlainUsername, saslPlainPassword sql.NullString
|
var nsAccount, nsPassword sql.NullString
|
||||||
var tls sql.NullBool
|
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)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
n.TLS = tls.Bool
|
n.TLS = tls.Bool
|
||||||
n.Pass = pass.String
|
n.Pass = pass.String
|
||||||
if connectCommands.Valid {
|
n.InviteCommand = inviteCmd.String
|
||||||
n.ConnectCommands = strings.Split(connectCommands.String, "\r\n")
|
n.NickServ.Account = nsAccount.String
|
||||||
}
|
n.NickServ.Password = nsPassword.String
|
||||||
n.SASL.Mechanism = saslMechanism.String
|
|
||||||
n.SASL.Plain.Username = saslPlainUsername.String
|
|
||||||
n.SASL.Plain.Password = saslPlainPassword.String
|
|
||||||
|
|
||||||
return &n, nil
|
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) {
|
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 {
|
if err != nil {
|
||||||
log.Fatal().Err(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
|
@ -95,20 +91,16 @@ func (ir *IrcRepo) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var net domain.IrcNetwork
|
var net domain.IrcNetwork
|
||||||
|
|
||||||
//var username, realname, pass, connectCommands sql.NullString
|
var pass, inviteCmd sql.NullString
|
||||||
var pass, connectCommands sql.NullString
|
|
||||||
var tls sql.NullBool
|
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)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
net.TLS = tls.Bool
|
net.TLS = tls.Bool
|
||||||
net.Pass = pass.String
|
net.Pass = pass.String
|
||||||
|
net.InviteCommand = inviteCmd.String
|
||||||
if connectCommands.Valid {
|
|
||||||
net.ConnectCommands = strings.Split(connectCommands.String, "\r\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
networks = append(networks, net)
|
networks = append(networks, net)
|
||||||
}
|
}
|
||||||
|
@ -131,7 +123,6 @@ func (ir *IrcRepo) ListChannels(networkID int64) ([]domain.IrcChannel, error) {
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var ch domain.IrcChannel
|
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 {
|
if err := rows.Scan(&ch.ID, &ch.Name, &ch.Enabled); err != nil {
|
||||||
log.Fatal().Err(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
|
@ -149,20 +140,10 @@ func (ir *IrcRepo) StoreNetwork(network *domain.IrcNetwork) error {
|
||||||
|
|
||||||
netName := toNullString(network.Name)
|
netName := toNullString(network.Name)
|
||||||
pass := toNullString(network.Pass)
|
pass := toNullString(network.Pass)
|
||||||
connectCommands := toNullString(strings.Join(network.ConnectCommands, "\r\n"))
|
inviteCmd := toNullString(network.InviteCommand)
|
||||||
|
|
||||||
var saslMechanism, saslPlainUsername, saslPlainPassword sql.NullString
|
nsAccount := toNullString(network.NickServ.Account)
|
||||||
if network.SASL.Mechanism != "" {
|
nsPassword := toNullString(network.NickServ.Password)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if network.ID != 0 {
|
if network.ID != 0 {
|
||||||
|
@ -170,26 +151,24 @@ func (ir *IrcRepo) StoreNetwork(network *domain.IrcNetwork) error {
|
||||||
_, err = ir.db.Exec(`UPDATE irc_network
|
_, err = ir.db.Exec(`UPDATE irc_network
|
||||||
SET enabled = ?,
|
SET enabled = ?,
|
||||||
name = ?,
|
name = ?,
|
||||||
addr = ?,
|
server = ?,
|
||||||
|
port = ?,
|
||||||
tls = ?,
|
tls = ?,
|
||||||
nick = ?,
|
|
||||||
pass = ?,
|
pass = ?,
|
||||||
connect_commands = ?,
|
invite_command = ?,
|
||||||
sasl_mechanism = ?,
|
nickserv_account = ?,
|
||||||
sasl_plain_username = ?,
|
nickserv_password = ?,
|
||||||
sasl_plain_password = ?,
|
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = ?`,
|
WHERE id = ?`,
|
||||||
network.Enabled,
|
network.Enabled,
|
||||||
netName,
|
netName,
|
||||||
network.Addr,
|
network.Server,
|
||||||
|
network.Port,
|
||||||
network.TLS,
|
network.TLS,
|
||||||
network.Nick,
|
|
||||||
pass,
|
pass,
|
||||||
connectCommands,
|
inviteCmd,
|
||||||
saslMechanism,
|
nsAccount,
|
||||||
saslPlainUsername,
|
nsPassword,
|
||||||
saslPlainPassword,
|
|
||||||
network.ID,
|
network.ID,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -198,25 +177,23 @@ func (ir *IrcRepo) StoreNetwork(network *domain.IrcNetwork) error {
|
||||||
res, err = ir.db.Exec(`INSERT INTO irc_network (
|
res, err = ir.db.Exec(`INSERT INTO irc_network (
|
||||||
enabled,
|
enabled,
|
||||||
name,
|
name,
|
||||||
addr,
|
server,
|
||||||
|
port,
|
||||||
tls,
|
tls,
|
||||||
nick,
|
|
||||||
pass,
|
pass,
|
||||||
connect_commands,
|
invite_command,
|
||||||
sasl_mechanism,
|
nickserv_account,
|
||||||
sasl_plain_username,
|
nickserv_password
|
||||||
sasl_plain_password
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
||||||
network.Enabled,
|
network.Enabled,
|
||||||
netName,
|
netName,
|
||||||
network.Addr,
|
network.Server,
|
||||||
|
network.Port,
|
||||||
network.TLS,
|
network.TLS,
|
||||||
network.Nick,
|
|
||||||
pass,
|
pass,
|
||||||
connectCommands,
|
inviteCmd,
|
||||||
saslMechanism,
|
nsAccount,
|
||||||
saslPlainUsername,
|
nsPassword,
|
||||||
saslPlainPassword,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Stack().Err(err).Msg("error executing query")
|
log.Error().Stack().Err(err).Msg("error executing query")
|
||||||
|
|
|
@ -24,7 +24,8 @@ CREATE TABLE indexer
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
settings TEXT,
|
settings TEXT,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE (identifier)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE irc_network
|
CREATE TABLE irc_network
|
||||||
|
@ -32,29 +33,30 @@ CREATE TABLE irc_network
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
enabled BOOLEAN,
|
enabled BOOLEAN,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
addr TEXT NOT NULL,
|
server TEXT NOT NULL,
|
||||||
nick TEXT NOT NULL,
|
port INTEGER NOT NULL,
|
||||||
tls BOOLEAN,
|
tls BOOLEAN,
|
||||||
pass TEXT,
|
pass TEXT,
|
||||||
connect_commands TEXT,
|
invite_command TEXT,
|
||||||
sasl_mechanism TEXT,
|
nickserv_account TEXT,
|
||||||
sasl_plain_username TEXT,
|
nickserv_password TEXT,
|
||||||
sasl_plain_password TEXT,
|
connected BOOLEAN,
|
||||||
|
connected_since TIMESTAMP,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
unique (addr, nick)
|
UNIQUE (server, port, nickserv_account)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE irc_channel
|
CREATE TABLE irc_channel
|
||||||
(
|
(
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
enabled BOOLEAN,
|
enabled BOOLEAN,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
password TEXT,
|
password TEXT,
|
||||||
detached BOOLEAN,
|
detached BOOLEAN,
|
||||||
network_id INTEGER NOT NULL,
|
network_id INTEGER NOT NULL,
|
||||||
FOREIGN KEY (network_id) REFERENCES irc_network(id),
|
FOREIGN KEY (network_id) REFERENCES irc_network(id),
|
||||||
unique (network_id, name)
|
UNIQUE (network_id, name)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE filter
|
CREATE TABLE filter
|
||||||
|
|
|
@ -9,7 +9,7 @@ type IndexerRepo interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Indexer struct {
|
type Indexer struct {
|
||||||
ID int `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Identifier string `json:"identifier"`
|
Identifier string `json:"identifier"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
|
@ -39,15 +39,21 @@ type IndexerSetting struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Value string `json:"value,omitempty"`
|
Value string `json:"value,omitempty"`
|
||||||
Label string `json:"label"`
|
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"`
|
Regex string `json:"regex,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type IndexerIRC struct {
|
type IndexerIRC struct {
|
||||||
Network string
|
Network string `json:"network"`
|
||||||
Server string
|
Server string `json:"server"`
|
||||||
Channels []string
|
Port int `json:"port"`
|
||||||
Announcers []string
|
TLS bool `json:"tls"`
|
||||||
|
Channels []string `json:"channels"`
|
||||||
|
Announcers []string `json:"announcers"`
|
||||||
|
SettingsMap map[string]string `json:"-"`
|
||||||
|
Settings []IndexerSetting `json:"settings"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type IndexerParse struct {
|
type IndexerParse struct {
|
||||||
|
|
|
@ -1,35 +1,37 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import "context"
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type IrcChannel struct {
|
type IrcChannel struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
Detached bool `json:"detached"`
|
Name string `json:"name"`
|
||||||
Name string `json:"name"`
|
Password string `json:"password"`
|
||||||
Password string `json:"password"`
|
Detached bool `json:"detached"`
|
||||||
|
Monitoring bool `json:"monitoring"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SASL struct {
|
type NickServ struct {
|
||||||
Mechanism string `json:"mechanism,omitempty"`
|
Account string `json:"account,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
Plain struct {
|
|
||||||
Username string `json:"username,omitempty"`
|
|
||||||
Password string `json:"password,omitempty"`
|
|
||||||
} `json:"plain,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type IrcNetwork struct {
|
type IrcNetwork struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
Addr string `json:"addr"`
|
Server string `json:"server"`
|
||||||
TLS bool `json:"tls"`
|
Port int `json:"port"`
|
||||||
Nick string `json:"nick"`
|
TLS bool `json:"tls"`
|
||||||
Pass string `json:"pass"`
|
Pass string `json:"pass"`
|
||||||
ConnectCommands []string `json:"connect_commands"`
|
InviteCommand string `json:"invite_command"`
|
||||||
SASL SASL `json:"sasl,omitempty"`
|
NickServ NickServ `json:"nickserv,omitempty"`
|
||||||
Channels []IrcChannel `json:"channels"`
|
Channels []IrcChannel `json:"channels"`
|
||||||
|
Connected bool `json:"connected"`
|
||||||
|
ConnectedSince *time.Time `json:"connected_since"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type IrcRepo interface {
|
type IrcRepo interface {
|
||||||
|
|
|
@ -183,7 +183,7 @@ func (s *service) Update(filter domain.Filter) (*domain.Filter, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, i := range filter.Indexers {
|
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)
|
log.Error().Err(err).Msgf("could not store filter indexer connections: %v", filter.Name)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
@ -17,97 +18,96 @@ type actionService interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type actionHandler struct {
|
type actionHandler struct {
|
||||||
encoder encoder
|
encoder encoder
|
||||||
actionService actionService
|
service actionService
|
||||||
|
}
|
||||||
|
|
||||||
|
func newActionHandler(encoder encoder, service actionService) *actionHandler {
|
||||||
|
return &actionHandler{
|
||||||
|
encoder: encoder,
|
||||||
|
service: service,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h actionHandler) Routes(r chi.Router) {
|
func (h actionHandler) Routes(r chi.Router) {
|
||||||
r.Get("/", h.getActions)
|
r.Get("/", h.getActions)
|
||||||
r.Post("/", h.storeAction)
|
r.Post("/", h.storeAction)
|
||||||
r.Delete("/{actionID}", h.deleteAction)
|
r.Delete("/{id}", h.deleteAction)
|
||||||
r.Put("/{actionID}", h.updateAction)
|
r.Put("/{id}", h.updateAction)
|
||||||
r.Patch("/{actionID}/toggleEnabled", h.toggleActionEnabled)
|
r.Patch("/{id}/toggleEnabled", h.toggleActionEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h actionHandler) getActions(w http.ResponseWriter, r *http.Request) {
|
func (h actionHandler) getActions(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
actions, err := h.service.Fetch()
|
||||||
|
|
||||||
actions, err := h.actionService.Fetch()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// encode error
|
// 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) {
|
func (h actionHandler) storeAction(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var data domain.Action
|
||||||
ctx = r.Context()
|
|
||||||
data domain.Action
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||||
// encode error
|
// encode error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
action, err := h.actionService.Store(data)
|
action, err := h.service.Store(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// encode error
|
// 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) {
|
func (h actionHandler) updateAction(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var data domain.Action
|
||||||
ctx = r.Context()
|
|
||||||
data domain.Action
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||||
// encode error
|
// encode error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
action, err := h.actionService.Store(data)
|
action, err := h.service.Store(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// encode error
|
// 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) {
|
func (h actionHandler) deleteAction(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
actionID, err := parseInt(chi.URLParam(r, "id"))
|
||||||
ctx = r.Context()
|
if err != nil {
|
||||||
actionID = chi.URLParam(r, "actionID")
|
h.encoder.StatusResponse(r.Context(), w, errors.New("bad param id"), http.StatusBadRequest)
|
||||||
)
|
}
|
||||||
|
|
||||||
// if !actionID return error
|
if err := h.service.Delete(actionID); err != nil {
|
||||||
|
|
||||||
id, _ := strconv.Atoi(actionID)
|
|
||||||
|
|
||||||
if err := h.actionService.Delete(id); err != nil {
|
|
||||||
// encode error
|
// 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) {
|
func (h actionHandler) toggleActionEnabled(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
actionID, err := parseInt(chi.URLParam(r, "id"))
|
||||||
ctx = r.Context()
|
if err != nil {
|
||||||
actionID = chi.URLParam(r, "actionID")
|
h.encoder.StatusResponse(r.Context(), w, errors.New("bad param id"), http.StatusBadRequest)
|
||||||
)
|
}
|
||||||
|
|
||||||
// if !actionID return error
|
if err := h.service.ToggleEnabled(actionID); err != nil {
|
||||||
|
|
||||||
id, _ := strconv.Atoi(actionID)
|
|
||||||
|
|
||||||
if err := h.actionService.ToggleEnabled(id); err != nil {
|
|
||||||
// encode error
|
// 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
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,15 @@ type authService interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type authHandler struct {
|
type authHandler struct {
|
||||||
encoder encoder
|
encoder encoder
|
||||||
authService authService
|
service authService
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAuthHandler(encoder encoder, service authService) *authHandler {
|
||||||
|
return &authHandler{
|
||||||
|
encoder: encoder,
|
||||||
|
service: service,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -49,7 +56,7 @@ func (h authHandler) login(w http.ResponseWriter, r *http.Request) {
|
||||||
store.Options.SameSite = http.SameSiteStrictMode
|
store.Options.SameSite = http.SameSiteStrictMode
|
||||||
session, _ := store.Get(r, "user_session")
|
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 {
|
if err != nil {
|
||||||
h.encoder.StatusResponse(ctx, w, nil, http.StatusUnauthorized)
|
h.encoder.StatusResponse(ctx, w, nil, http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
|
|
|
@ -20,6 +20,10 @@ type configHandler struct {
|
||||||
encoder encoder
|
encoder encoder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newConfigHandler(encoder encoder) *configHandler {
|
||||||
|
return &configHandler{encoder: encoder}
|
||||||
|
}
|
||||||
|
|
||||||
func (h configHandler) Routes(r chi.Router) {
|
func (h configHandler) Routes(r chi.Router) {
|
||||||
r.Get("/", h.getConfig)
|
r.Get("/", h.getConfig)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,15 @@ type downloadClientService interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type downloadClientHandler struct {
|
type downloadClientHandler struct {
|
||||||
encoder encoder
|
encoder encoder
|
||||||
downloadClientService downloadClientService
|
service downloadClientService
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDownloadClientHandler(encoder encoder, service downloadClientService) *downloadClientHandler {
|
||||||
|
return &downloadClientHandler{
|
||||||
|
encoder: encoder,
|
||||||
|
service: service,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h downloadClientHandler) Routes(r chi.Router) {
|
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) {
|
func (h downloadClientHandler) listDownloadClients(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
clients, err := h.downloadClientService.List()
|
clients, err := h.service.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
@ -52,7 +59,7 @@ func (h downloadClientHandler) store(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := h.downloadClientService.Store(data)
|
client, err := h.service.Store(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// encode error
|
// encode error
|
||||||
}
|
}
|
||||||
|
@ -72,7 +79,7 @@ func (h downloadClientHandler) test(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.downloadClientService.Test(data)
|
err := h.service.Test(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// encode error
|
// encode error
|
||||||
h.encoder.StatusResponse(ctx, w, nil, http.StatusBadRequest)
|
h.encoder.StatusResponse(ctx, w, nil, http.StatusBadRequest)
|
||||||
|
@ -93,7 +100,7 @@ func (h downloadClientHandler) update(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := h.downloadClientService.Store(data)
|
client, err := h.service.Store(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// encode error
|
// encode error
|
||||||
}
|
}
|
||||||
|
@ -111,7 +118,7 @@ func (h downloadClientHandler) delete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
id, _ := strconv.Atoi(clientID)
|
id, _ := strconv.Atoi(clientID)
|
||||||
|
|
||||||
if err := h.downloadClientService.Delete(id); err != nil {
|
if err := h.service.Delete(id); err != nil {
|
||||||
// encode error
|
// encode error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,15 @@ type filterService interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type filterHandler struct {
|
type filterHandler struct {
|
||||||
encoder encoder
|
encoder encoder
|
||||||
filterService filterService
|
service filterService
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFilterHandler(encoder encoder, service filterService) *filterHandler {
|
||||||
|
return &filterHandler{
|
||||||
|
encoder: encoder,
|
||||||
|
service: service,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h filterHandler) Routes(r chi.Router) {
|
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) {
|
func (h filterHandler) getFilters(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
trackers, err := h.filterService.ListFilters()
|
trackers, err := h.service.ListFilters()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
@ -51,7 +58,7 @@ func (h filterHandler) getByID(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
id, _ := strconv.Atoi(filterID)
|
id, _ := strconv.Atoi(filterID)
|
||||||
|
|
||||||
filter, err := h.filterService.FindByID(id)
|
filter, err := h.service.FindByID(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.encoder.StatusNotFound(ctx, w)
|
h.encoder.StatusNotFound(ctx, w)
|
||||||
return
|
return
|
||||||
|
@ -68,7 +75,7 @@ func (h filterHandler) storeFilterAction(w http.ResponseWriter, r *http.Request)
|
||||||
|
|
||||||
id, _ := strconv.Atoi(filterID)
|
id, _ := strconv.Atoi(filterID)
|
||||||
|
|
||||||
filter, err := h.filterService.FindByID(id)
|
filter, err := h.service.FindByID(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
@ -87,7 +94,7 @@ func (h filterHandler) store(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
filter, err := h.filterService.Store(data)
|
filter, err := h.service.Store(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// encode error
|
// encode error
|
||||||
return
|
return
|
||||||
|
@ -107,7 +114,7 @@ func (h filterHandler) update(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
filter, err := h.filterService.Update(data)
|
filter, err := h.service.Update(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// encode error
|
// encode error
|
||||||
return
|
return
|
||||||
|
@ -124,7 +131,7 @@ func (h filterHandler) delete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
id, _ := strconv.Atoi(filterID)
|
id, _ := strconv.Atoi(filterID)
|
||||||
|
|
||||||
if err := h.filterService.Delete(id); err != nil {
|
if err := h.service.Delete(id); err != nil {
|
||||||
// return err
|
// return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,17 @@ type indexerService interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type indexerHandler struct {
|
type indexerHandler struct {
|
||||||
encoder encoder
|
encoder encoder
|
||||||
indexerService indexerService
|
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) {
|
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) {
|
func (h indexerHandler) getSchema(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
indexers, err := h.indexerService.GetTemplates()
|
indexers, err := h.service.GetTemplates()
|
||||||
if err != nil {
|
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) {
|
func (h indexerHandler) store(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var data domain.Indexer
|
||||||
ctx = r.Context()
|
|
||||||
data domain.Indexer
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
indexer, err := h.indexerService.Store(data)
|
indexer, err := h.service.Store(data)
|
||||||
if err != nil {
|
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) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
indexer, err := h.indexerService.Update(data)
|
indexer, err := h.service.Update(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
@ -88,7 +96,7 @@ func (h indexerHandler) delete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
id, _ := strconv.Atoi(idParam)
|
id, _ := strconv.Atoi(idParam)
|
||||||
|
|
||||||
if err := h.indexerService.Delete(id); err != nil {
|
if err := h.service.Delete(id); err != nil {
|
||||||
// return err
|
// 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) {
|
func (h indexerHandler) getAll(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
indexers, err := h.indexerService.GetAll()
|
indexers, err := h.service.GetAll()
|
||||||
if err != nil {
|
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) {
|
func (h indexerHandler) list(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
indexers, err := h.indexerService.List()
|
indexers, err := h.service.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,15 @@ type ircService interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ircHandler struct {
|
type ircHandler struct {
|
||||||
encoder encoder
|
encoder encoder
|
||||||
ircService ircService
|
service ircService
|
||||||
|
}
|
||||||
|
|
||||||
|
func newIrcHandler(encoder encoder, service ircService) *ircHandler {
|
||||||
|
return &ircHandler{
|
||||||
|
encoder: encoder,
|
||||||
|
service: service,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h ircHandler) Routes(r chi.Router) {
|
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) {
|
func (h ircHandler) listNetworks(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
networks, err := h.ircService.ListNetworks(ctx)
|
networks, err := h.service.ListNetworks(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
@ -54,7 +61,7 @@ func (h ircHandler) getNetworkByID(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
id, _ := strconv.Atoi(networkID)
|
id, _ := strconv.Atoi(networkID)
|
||||||
|
|
||||||
network, err := h.ircService.GetNetworkByID(int64(id))
|
network, err := h.service.GetNetworkByID(int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
@ -72,9 +79,11 @@ func (h ircHandler) storeNetwork(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.ircService.StoreNetwork(&data)
|
err := h.service.StoreNetwork(&data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//
|
//
|
||||||
|
h.encoder.StatusResponse(ctx, w, nil, http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.encoder.StatusResponse(ctx, w, nil, http.StatusCreated)
|
h.encoder.StatusResponse(ctx, w, nil, http.StatusCreated)
|
||||||
|
@ -93,7 +102,7 @@ func (h ircHandler) storeChannel(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.ircService.StoreChannel(int64(id), &data)
|
err := h.service.StoreChannel(int64(id), &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
@ -107,7 +116,7 @@ func (h ircHandler) stopNetwork(w http.ResponseWriter, r *http.Request) {
|
||||||
networkID = chi.URLParam(r, "networkID")
|
networkID = chi.URLParam(r, "networkID")
|
||||||
)
|
)
|
||||||
|
|
||||||
err := h.ircService.StopNetwork(networkID)
|
err := h.service.StopNetwork(networkID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
@ -123,7 +132,7 @@ func (h ircHandler) deleteNetwork(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
id, _ := strconv.Atoi(networkID)
|
id, _ := strconv.Atoi(networkID)
|
||||||
|
|
||||||
err := h.ircService.DeleteNetwork(ctx, int64(id))
|
err := h.service.DeleteNetwork(ctx, int64(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,56 +64,19 @@ func (s Server) Handler() http.Handler {
|
||||||
fileSystem.ServeHTTP(w, r)
|
fileSystem.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
|
|
||||||
authHandler := authHandler{
|
r.Route("/api/auth", newAuthHandler(encoder, s.authService).Routes)
|
||||||
encoder: encoder,
|
|
||||||
authService: s.authService,
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Route("/api/auth", authHandler.Routes)
|
|
||||||
|
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(IsAuthenticated)
|
r.Use(IsAuthenticated)
|
||||||
|
|
||||||
actionHandler := actionHandler{
|
r.Route("/api", func(r chi.Router) {
|
||||||
encoder: encoder,
|
r.Route("/actions", newActionHandler(encoder, s.actionService).Routes)
|
||||||
actionService: s.actionService,
|
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("/api/actions", actionHandler.Routes)
|
r.Route("/irc", newIrcHandler(encoder, s.ircService).Routes)
|
||||||
|
r.Route("/indexer", newIndexerHandler(encoder, s.indexerService, s.ircService).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.HandleFunc("/*", handler.ServeHTTP)
|
//r.HandleFunc("/*", handler.ServeHTTP)
|
|
@ -14,31 +14,46 @@ supports:
|
||||||
source: gazelle
|
source: gazelle
|
||||||
settings:
|
settings:
|
||||||
- name: authkey
|
- name: authkey
|
||||||
type: text
|
type: secret
|
||||||
label: Auth key
|
label: Auth key
|
||||||
tooltip: Right click DL on a torrent and get the authkey.
|
help: Right click DL on a torrent and get the authkey.
|
||||||
description: Right click DL on a torrent and get the authkey.
|
|
||||||
- name: torrent_pass
|
- name: torrent_pass
|
||||||
type: text
|
type: secret
|
||||||
label: Torrent pass
|
label: Torrent pass
|
||||||
tooltip: Right click DL on a torrent and get the torrent_pass.
|
help: Right click DL on a torrent and get the torrent_pass.
|
||||||
description: Right click DL on a torrent and get the torrent_pass.
|
|
||||||
|
|
||||||
irc:
|
irc:
|
||||||
network: AlphaRatio
|
network: AlphaRatio
|
||||||
server: irc.alpharatio.cc:6697
|
server: irc.alpharatio.cc
|
||||||
port: 6697
|
port: 6697
|
||||||
|
tls: true
|
||||||
channels:
|
channels:
|
||||||
- "#Announce"
|
- "#Announce"
|
||||||
announcers:
|
announcers:
|
||||||
- Voyager
|
- 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:
|
parse:
|
||||||
type: multi
|
type: multi
|
||||||
lines:
|
lines:
|
||||||
-
|
- test:
|
||||||
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. ]"
|
||||||
- "[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. ]"
|
|
||||||
pattern: \[New Release\]-\[(.*)\]-\[(.*)\]-\[URL\]-\[ (https?://.*)id=\d+ \]-\[ (\d+) \](?:-\[ Uploaded (.*) after pre. ])?
|
pattern: \[New Release\]-\[(.*)\]-\[(.*)\]-\[URL\]-\[ (https?://.*)id=\d+ \]-\[ (\d+) \](?:-\[ Uploaded (.*) after pre. ])?
|
||||||
vars:
|
vars:
|
||||||
- category
|
- category
|
||||||
|
@ -46,9 +61,8 @@ parse:
|
||||||
- baseUrl
|
- baseUrl
|
||||||
- torrentId
|
- torrentId
|
||||||
- preTime
|
- preTime
|
||||||
-
|
- test:
|
||||||
test:
|
- "[AutoDL]-[MovieHD]-[000000]-[ 1 | 10659 | 1 | 1 ]-[That.Movie.2017.INTERNAL.1080p.BluRay.CRF.x264-GROUP]"
|
||||||
- "[AutoDL]-[MovieHD]-[699434]-[ 1 | 10659 | 1 | 1 ]-[War.For.The.Planet.Of.The.Apes.2017.INTERNAL.1080p.BluRay.CRF.x264-SAPHiRE]"
|
|
||||||
pattern: \[AutoDL\]-\[.*\]-\[.*\]-\[ ([01]) \| (\d+) \| ([01]) \| ([01]) \]-\[.+\]
|
pattern: \[AutoDL\]-\[.*\]-\[.*\]-\[ ([01]) \| (\d+) \| ([01]) \| ([01]) \]-\[.+\]
|
||||||
vars:
|
vars:
|
||||||
- scene
|
- scene
|
||||||
|
|
|
@ -13,28 +13,44 @@ supports:
|
||||||
- rss
|
- rss
|
||||||
source: UNIT3D (F3NIX)
|
source: UNIT3D (F3NIX)
|
||||||
settings:
|
settings:
|
||||||
- name: passkey
|
- name: rsskey
|
||||||
type: text
|
type: secret
|
||||||
label: Passkey
|
label: RSS key
|
||||||
tooltip: The passkey in your BeyondHD RSS feed.
|
help: "Go to your profile, My Security, RSS Key and copy RSS key."
|
||||||
description: "Go to your profile and copy and paste your RSS link to extract the rsskey."
|
|
||||||
|
|
||||||
irc:
|
irc:
|
||||||
network: BeyondHD-IRC
|
network: BeyondHD-IRC
|
||||||
server: irc.beyond-hd.me:6697
|
server: irc.beyond-hd.me
|
||||||
port: 6697
|
port: 6697
|
||||||
|
tls: true
|
||||||
channels:
|
channels:
|
||||||
- "#bhd_announce"
|
- "#bhd_announce"
|
||||||
announcers:
|
announcers:
|
||||||
- Willie
|
- Willie
|
||||||
- Millie
|
- 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:
|
parse:
|
||||||
type: single
|
type: single
|
||||||
lines:
|
lines:
|
||||||
-
|
- test:
|
||||||
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"
|
||||||
- "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"
|
|
||||||
pattern: 'New Torrent:(.*)Category:(.*)By:(.*)Size:(.*)Link: https?\:\/\/([^\/]+\/).*[&\?]id=(\d+)'
|
pattern: 'New Torrent:(.*)Category:(.*)By:(.*)Size:(.*)Link: https?\:\/\/([^\/]+\/).*[&\?]id=(\d+)'
|
||||||
vars:
|
vars:
|
||||||
- torrentName
|
- torrentName
|
||||||
|
|
|
@ -13,42 +13,57 @@ supports:
|
||||||
- rss
|
- rss
|
||||||
source: gazelle
|
source: gazelle
|
||||||
settings:
|
settings:
|
||||||
- name: authkey
|
- name: authkey
|
||||||
type: text
|
type: secret
|
||||||
label: Auth key
|
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
|
- name: torrent_pass
|
||||||
type: text
|
type: secret
|
||||||
label: Torrent pass
|
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:
|
irc:
|
||||||
network: BroadcasTheNet
|
network: BroadcasTheNet
|
||||||
server: irc.broadcasthenet.net:6697
|
server: irc.broadcasthenet.net
|
||||||
port: 6697
|
port: 6697
|
||||||
|
tls: true
|
||||||
channels:
|
channels:
|
||||||
- "#BTN-Announce"
|
- "#BTN-Announce"
|
||||||
announcers:
|
announcers:
|
||||||
- Barney
|
- 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:
|
parse:
|
||||||
type: multi
|
type: multi
|
||||||
lines:
|
lines:
|
||||||
-
|
- test:
|
||||||
test:
|
- "NOW BROADCASTING! [ The Show S06E07 720p WEB-DL DD 5.1 H.264 - LP ]"
|
||||||
- "NOW BROADCASTING! [ Lost S06E07 720p WEB-DL DD 5.1 H.264 - LP ]"
|
|
||||||
pattern: ^NOW BROADCASTING! \[(.*)\]
|
pattern: ^NOW BROADCASTING! \[(.*)\]
|
||||||
vars:
|
vars:
|
||||||
- torrentName
|
- torrentName
|
||||||
-
|
- test:
|
||||||
test:
|
- "[ Title: S06E07 ] [ Series: The Show ]"
|
||||||
- "[ Title: S06E07 ] [ Series: Lost ]"
|
|
||||||
pattern: '^\[ Title: (.*) \] \[ Series: (.*) \]'
|
pattern: '^\[ Title: (.*) \] \[ Series: (.*) \]'
|
||||||
vars:
|
vars:
|
||||||
- title
|
- title
|
||||||
- name1
|
- name1
|
||||||
-
|
- test:
|
||||||
test:
|
|
||||||
- "[ 2010 ] [ Episode ] [ MKV | x264 | WEB ] [ Uploader: Uploader1 ]"
|
- "[ 2010 ] [ Episode ] [ MKV | x264 | WEB ] [ Uploader: Uploader1 ]"
|
||||||
pattern: '^(?:\[ (\d+) \] )?\[ (.*) \] \[ (.*) \] \[ Uploader: (.*?) \](?: \[ Pretime: (.*) \])?'
|
pattern: '^(?:\[ (\d+) \] )?\[ (.*) \] \[ (.*) \] \[ Uploader: (.*?) \](?: \[ Pretime: (.*) \])?'
|
||||||
vars:
|
vars:
|
||||||
|
@ -57,10 +72,9 @@ parse:
|
||||||
- tags
|
- tags
|
||||||
- uploader
|
- uploader
|
||||||
- preTime
|
- preTime
|
||||||
-
|
- test:
|
||||||
test:
|
|
||||||
- "[ https://XXXXXXXXX/torrents.php?id=7338 / https://XXXXXXXXX/torrents.php?action=download&id=9116 ]"
|
- "[ https://XXXXXXXXX/torrents.php?id=7338 / https://XXXXXXXXX/torrents.php?action=download&id=9116 ]"
|
||||||
pattern: ^\[ .* / (https?://.*id=\d+) \]
|
pattern: ^\[ .* \/ (https?:\/\/.*id=\d+) \]
|
||||||
vars:
|
vars:
|
||||||
- baseUrl
|
- baseUrl
|
||||||
|
|
||||||
|
|
|
@ -14,28 +14,39 @@ supports:
|
||||||
source: gazelle
|
source: gazelle
|
||||||
settings:
|
settings:
|
||||||
- name: authkey
|
- name: authkey
|
||||||
type: text
|
type: secret
|
||||||
label: Auth key
|
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
|
- name: torrent_pass
|
||||||
type: text
|
type: secret
|
||||||
label: Torrent pass
|
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:
|
irc:
|
||||||
network: DigitalIRC
|
network: DigitalIRC
|
||||||
server: irc.empornium.is:6697
|
server: irc.empornium.is
|
||||||
port: 6697
|
port: 6697
|
||||||
|
tls: true
|
||||||
channels:
|
channels:
|
||||||
- "#empornium-announce"
|
- "#empornium-announce"
|
||||||
announcers:
|
announcers:
|
||||||
- "^Wizard^"
|
- "^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:
|
parse:
|
||||||
type: single
|
type: single
|
||||||
lines:
|
lines:
|
||||||
-
|
- pattern: '^(.*?) - Size: ([0-9]+?.*?) - Uploader: (.*?) - Tags: (.*?) - (https://.*torrents.php\?)id=(.*)$'
|
||||||
pattern: '^(.*?) - Size: ([0-9]+?.*?) - Uploader: (.*?) - Tags: (.*?) - (https://.*torrents.php\?)id=(.*)$'
|
|
||||||
vars:
|
vars:
|
||||||
- torrentName
|
- torrentName
|
||||||
- torrentSize
|
- torrentSize
|
||||||
|
|
|
@ -14,28 +14,38 @@ supports:
|
||||||
source: custom
|
source: custom
|
||||||
settings:
|
settings:
|
||||||
- name: passkey
|
- name: passkey
|
||||||
type: text
|
type: secret
|
||||||
label: Passkey
|
label: Passkey
|
||||||
tooltip: The passkey in your profile.
|
help: "The passkey in your profile."
|
||||||
description: "The passkey in your profile."
|
|
||||||
|
|
||||||
irc:
|
irc:
|
||||||
network: FileList
|
network: FileList
|
||||||
server: irc.filelist.io:6697
|
server: irc.filelist.io
|
||||||
port: 6697
|
port: 6697
|
||||||
|
tls: true
|
||||||
channels:
|
channels:
|
||||||
- "#announce"
|
- "#announce"
|
||||||
announcers:
|
announcers:
|
||||||
- Announce
|
- 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:
|
parse:
|
||||||
type: single
|
type: single
|
||||||
lines:
|
lines:
|
||||||
-
|
- test:
|
||||||
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.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=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=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=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=746789 -- by uploader1'
|
|
||||||
pattern: 'New Torrent: (.*?) (?:-- \[(FreeLeech!)] )?(?:-- \[(Internal!)] )?-- \[(.*)] \[(.*)] -- (https?:\/\/filelist.io\/).*id=(.*) -- by (.*)'
|
pattern: 'New Torrent: (.*?) (?:-- \[(FreeLeech!)] )?(?:-- \[(Internal!)] )?-- \[(.*)] \[(.*)] -- (https?:\/\/filelist.io\/).*id=(.*) -- by (.*)'
|
||||||
vars:
|
vars:
|
||||||
- torrentName
|
- torrentName
|
||||||
|
|
|
@ -14,32 +14,47 @@ supports:
|
||||||
source: gazelle
|
source: gazelle
|
||||||
settings:
|
settings:
|
||||||
- name: authkey
|
- name: authkey
|
||||||
type: text
|
type: secret
|
||||||
label: Auth key
|
label: Auth key
|
||||||
tooltip: Right click DL on a torrent and get the authkey.
|
help: Right click DL on a torrent and get the authkey.
|
||||||
description: Right click DL on a torrent and get the authkey.
|
|
||||||
- name: torrent_pass
|
- name: torrent_pass
|
||||||
type: text
|
type: secret
|
||||||
label: Torrent pass
|
label: Torrent pass
|
||||||
tooltip: Right click DL on a torrent and get the torrent_pass.
|
help: Right click DL on a torrent and get the torrent_pass.
|
||||||
description: Right click DL on a torrent and get the torrent_pass.
|
|
||||||
|
|
||||||
irc:
|
irc:
|
||||||
network: GGn
|
network: GGn
|
||||||
server: irc.gazellegames.net:7000
|
server: irc.gazellegames.net
|
||||||
port: 7000
|
port: 7000
|
||||||
|
tls: true
|
||||||
channels:
|
channels:
|
||||||
- "#GGn-Announce"
|
- "#GGn-Announce"
|
||||||
announcers:
|
announcers:
|
||||||
- Vertigo
|
- 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:
|
parse:
|
||||||
type: single
|
type: single
|
||||||
lines:
|
lines:
|
||||||
-
|
- test:
|
||||||
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 :-: 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 :-: Other.Game-HI2U in Other Game [2016] ::English, Scene:: FREELEECH! :: https://gazellegames.net/torrents.php?torrentid=00000 - action, adventure, casual, indie, role.playing.game;"
|
||||||
- "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;"
|
|
||||||
pattern: '^(.+) :-: (.+) :-: (.+) \[(\d+)\] ::(.+?):: ?(.+? ::)? https?:\/\/([^\/]+\/)torrents.php\?torrentid=(\d+) ?-? ?(.*?)?;?$'
|
pattern: '^(.+) :-: (.+) :-: (.+) \[(\d+)\] ::(.+?):: ?(.+? ::)? https?:\/\/([^\/]+\/)torrents.php\?torrentid=(\d+) ?-? ?(.*?)?;?$'
|
||||||
vars:
|
vars:
|
||||||
- uploader
|
- uploader
|
||||||
|
|
|
@ -14,26 +14,37 @@ supports:
|
||||||
- rss
|
- rss
|
||||||
source: xbtit
|
source: xbtit
|
||||||
settings:
|
settings:
|
||||||
- name: cookie
|
- name: cookie
|
||||||
type: text
|
type: text
|
||||||
label: Cookie
|
label: Cookie
|
||||||
description: "FireFox -> Preferences -> Privacy -> Show Cookies and find the uid and pass cookies. Example: uid=1234; pass=asdf12347asdf13"
|
help: "Check how to get cookies in your browser and find the uid and pass cookies. Example: uid=1234; pass=asdf12347asdf13"
|
||||||
|
|
||||||
irc:
|
irc:
|
||||||
network: P2P-NET
|
network: P2P-NET
|
||||||
server: irc.p2p-network.net:6697
|
server: irc.p2p-network.net
|
||||||
port: 6697
|
port: 6697
|
||||||
|
tls: true
|
||||||
channels:
|
channels:
|
||||||
- "#HD-Torrents.Announce"
|
- "#HD-Torrents.Announce"
|
||||||
announcers:
|
announcers:
|
||||||
- HoboLarry
|
- 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:
|
parse:
|
||||||
type: single
|
type: single
|
||||||
lines:
|
lines:
|
||||||
-
|
- test:
|
||||||
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"
|
||||||
- "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"
|
|
||||||
pattern: 'New Torrent in category \[([^\]]*)\] (.*) \(([^\)]*)\) uploaded! Download\: https?\:\/\/([^\/]+\/).*[&\?]id=([a-f0-9]+)'
|
pattern: 'New Torrent in category \[([^\]]*)\] (.*) \(([^\)]*)\) uploaded! Download\: https?\:\/\/([^\/]+\/).*[&\?]id=([a-f0-9]+)'
|
||||||
vars:
|
vars:
|
||||||
- category
|
- category
|
||||||
|
|
|
@ -15,29 +15,38 @@ supports:
|
||||||
source: unknown
|
source: unknown
|
||||||
settings:
|
settings:
|
||||||
- name: passkey
|
- name: passkey
|
||||||
type: text
|
type: secret
|
||||||
label: Passkey
|
label: Passkey
|
||||||
tooltip: Copy the passkey from your details page
|
help: "Copy the passkey from your details page."
|
||||||
description: "Copy the passkey from your details page."
|
|
||||||
|
|
||||||
irc:
|
irc:
|
||||||
network: IPTorrents
|
network: IPTorrents
|
||||||
server: irc.iptorrents.com:6697
|
server: irc.iptorrents.com
|
||||||
port: 6697
|
port: 6697
|
||||||
|
tls: true
|
||||||
channels:
|
channels:
|
||||||
- "#ipt.announce"
|
- "#ipt.announce"
|
||||||
- "#ipt.announce2"
|
|
||||||
announcers:
|
announcers:
|
||||||
- IPT
|
- IPT
|
||||||
- FunTimes
|
- 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:
|
parse:
|
||||||
type: single
|
type: single
|
||||||
lines:
|
lines:
|
||||||
-
|
- test:
|
||||||
test:
|
- "[Movies/XviD] The Movie 2010 DVDRip XviD-GROUP FREELEECH - http://www.iptorrents.com/details.php?id=000000 - 716.219 MB"
|
||||||
- "[Movie/XXX] Audrey Bitoni HD Pack FREELEECH - http://www.iptorrents.com/details.php?id=789421 - 14.112 GB"
|
- "[Movies/XviD] The Movie 2010 DVDRip XviD-GROUP - http://www.iptorrents.com/details.php?id=000000 - 716.219 MB"
|
||||||
- "[Movies/XviD] The First Men In The Moon 2010 DVDRip XviD-VoMiT - http://www.iptorrents.com/details.php?id=396589 - 716.219 MB"
|
|
||||||
pattern: '^\[([^\]]*)](.*?)\s*(FREELEECH)*\s*-\s+https?\:\/\/([^\/]+).*[&\?]id=(\d+)\s*-(.*)'
|
pattern: '^\[([^\]]*)](.*?)\s*(FREELEECH)*\s*-\s+https?\:\/\/([^\/]+).*[&\?]id=(\d+)\s*-(.*)'
|
||||||
vars:
|
vars:
|
||||||
- category
|
- category
|
||||||
|
|
|
@ -13,33 +13,41 @@ supports:
|
||||||
- rss
|
- rss
|
||||||
source: gazelle
|
source: gazelle
|
||||||
settings:
|
settings:
|
||||||
- name: authkey
|
- name: authkey
|
||||||
type: text
|
type: secret
|
||||||
label: Auth key
|
label: Auth key
|
||||||
tooltip: Right click DL on a torrent and get the authkey.
|
help: Right click DL on a torrent and get the authkey.
|
||||||
description: Right click DL on a torrent and get the authkey.
|
- name: torrent_pass
|
||||||
- name: torrent_pass
|
type: secret
|
||||||
type: text
|
label: Torrent pass
|
||||||
label: Torrent pass
|
help: Right click DL on a torrent and get the 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.
|
|
||||||
|
|
||||||
irc:
|
irc:
|
||||||
network: Nebulance
|
network: Nebulance
|
||||||
server: irc.nebulance.cc:6697
|
server: irc.nebulance.cc
|
||||||
port: 6697
|
port: 6697
|
||||||
channels:
|
channels:
|
||||||
- "#nbl-announce"
|
- "#nbl-announce"
|
||||||
announcers:
|
announcers:
|
||||||
- DRADIS
|
- 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:
|
parse:
|
||||||
type: single
|
type: single
|
||||||
lines:
|
lines:
|
||||||
-
|
- test:
|
||||||
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]"
|
||||||
- "[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] 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]"
|
||||||
- "[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]"
|
|
||||||
pattern: '\[(.*?)\] (.*?) \[(.*?)\] \[(.*?) - Uploader: (.*?)\] - (https?://.*)id=(\d+) \[Tags: (.*)\]'
|
pattern: '\[(.*?)\] (.*?) \[(.*?)\] \[(.*?) - Uploader: (.*?)\] - (https?://.*)id=(\d+) \[Tags: (.*)\]'
|
||||||
vars:
|
vars:
|
||||||
- category
|
- category
|
||||||
|
|
|
@ -13,33 +13,48 @@ supports:
|
||||||
- rss
|
- rss
|
||||||
source: gazelle
|
source: gazelle
|
||||||
settings:
|
settings:
|
||||||
- name: authkey
|
- name: authkey
|
||||||
type: text
|
type: text
|
||||||
label: Auth key
|
label: Auth key
|
||||||
tooltip: Right click DL on a torrent and get the authkey.
|
help: Right click DL on a torrent and get the authkey.
|
||||||
description: Right click DL on a torrent and get the authkey.
|
- name: torrent_pass
|
||||||
- name: torrent_pass
|
type: text
|
||||||
type: text
|
label: Torrent pass
|
||||||
label: Torrent pass
|
help: Right click DL on a torrent and get the 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.
|
|
||||||
|
|
||||||
irc:
|
irc:
|
||||||
network: Orpheus
|
network: Orpheus
|
||||||
server: irc.orpheus.network:7000
|
server: irc.orpheus.network
|
||||||
port: 7000
|
port: 7000
|
||||||
|
tls: true
|
||||||
channels:
|
channels:
|
||||||
- "#announce"
|
- "#announce"
|
||||||
announcers:
|
announcers:
|
||||||
- hermes
|
- 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:
|
parse:
|
||||||
type: single
|
type: single
|
||||||
lines:
|
lines:
|
||||||
-
|
- test:
|
||||||
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: 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: Something [2021] [Album] - FLAC / Lossless / CD - - https://orpheus.network/torrents.php?id=000000 / https://orpheus.network/torrents.php?action=download&id=0000000"
|
||||||
- "TORRENT: THE BOOK [2021] [Album] - FLAC / Lossless / CD - - https://orpheus.network/torrents.php?id=693523 / https://orpheus.network/torrents.php?action=download&id=1647867"
|
|
||||||
pattern: 'TORRENT: (.*) - (.*) - https?://.* / (https?://.*id=\d+)'
|
pattern: 'TORRENT: (.*) - (.*) - https?://.* / (https?://.*id=\d+)'
|
||||||
vars:
|
vars:
|
||||||
- torrentName
|
- torrentName
|
||||||
|
|
|
@ -13,33 +13,48 @@ supports:
|
||||||
- rss
|
- rss
|
||||||
source: gazelle
|
source: gazelle
|
||||||
settings:
|
settings:
|
||||||
- name: authkey
|
- name: authkey
|
||||||
type: text
|
type: secret
|
||||||
label: Auth key
|
label: Auth key
|
||||||
tooltip: Right click DL on a torrent and get the authkey.
|
help: Right click DL on a torrent and get the authkey.
|
||||||
description: Right click DL on a torrent and get the authkey.
|
- name: torrent_pass
|
||||||
- name: torrent_pass
|
type: secret
|
||||||
type: text
|
label: Torrent pass
|
||||||
label: Torrent pass
|
help: Right click DL on a torrent and get the 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.
|
|
||||||
|
|
||||||
irc:
|
irc:
|
||||||
network: PassThePopcorn
|
network: PassThePopcorn
|
||||||
server: irc.passthepopcorn.me:7000
|
server: irc.passthepopcorn.me
|
||||||
port: 7000
|
port: 7000
|
||||||
|
tls: true
|
||||||
channels:
|
channels:
|
||||||
- "#ptp-announce"
|
- "#ptp-announce"
|
||||||
announcers:
|
announcers:
|
||||||
- Hummingbird
|
- 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:
|
parse:
|
||||||
type: single
|
type: single
|
||||||
lines:
|
lines:
|
||||||
-
|
- test:
|
||||||
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"
|
||||||
- "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"
|
- "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"
|
||||||
- "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"
|
|
||||||
pattern: '^(.*)-\s*https?:.*[&\?]id=.*https?\:\/\/([^\/]+\/).*[&\?]id=(\d+)\s*-\s*(.*)'
|
pattern: '^(.*)-\s*https?:.*[&\?]id=.*https?\:\/\/([^\/]+\/).*[&\?]id=(\d+)\s*-\s*(.*)'
|
||||||
vars:
|
vars:
|
||||||
- torrentName
|
- torrentName
|
||||||
|
|
|
@ -13,33 +13,48 @@ supports:
|
||||||
- rss
|
- rss
|
||||||
source: gazelle
|
source: gazelle
|
||||||
settings:
|
settings:
|
||||||
- name: authkey
|
- name: authkey
|
||||||
type: text
|
type: secret
|
||||||
label: Auth key
|
label: Auth key
|
||||||
tooltip: Right click DL on a torrent and get the authkey.
|
help: Right click DL on a torrent and get the authkey.
|
||||||
description: Right click DL on a torrent and get the authkey.
|
- name: torrent_pass
|
||||||
- name: torrent_pass
|
type: secret
|
||||||
type: text
|
label: Torrent pass
|
||||||
label: Torrent pass
|
help: Right click DL on a torrent and get the 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.
|
|
||||||
|
|
||||||
irc:
|
irc:
|
||||||
network: Scratch-Network
|
network: Scratch-Network
|
||||||
server: irc.scratch-network.net:6697
|
server: irc.scratch-network.net
|
||||||
port: 6697
|
port: 6697
|
||||||
|
tls: true
|
||||||
channels:
|
channels:
|
||||||
- "#red-announce"
|
- "#red-announce"
|
||||||
announcers:
|
announcers:
|
||||||
- Drone
|
- 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:
|
parse:
|
||||||
type: single
|
type: single
|
||||||
lines:
|
lines:
|
||||||
-
|
- test:
|
||||||
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"
|
||||||
- "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"
|
- "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"
|
||||||
- "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"
|
|
||||||
pattern: '^(.*)\s+-\s+https?:.*[&\?]id=.*https?\:\/\/([^\/]+\/).*[&\?]id=(\d+)\s*-\s*(.*)'
|
pattern: '^(.*)\s+-\s+https?:.*[&\?]id=.*https?\:\/\/([^\/]+\/).*[&\?]id=(\d+)\s*-\s*(.*)'
|
||||||
vars:
|
vars:
|
||||||
- torrentName
|
- torrentName
|
||||||
|
|
|
@ -14,27 +14,37 @@ supports:
|
||||||
source: rartracker
|
source: rartracker
|
||||||
settings:
|
settings:
|
||||||
- name: passkey
|
- name: passkey
|
||||||
type: text
|
type: secret
|
||||||
label: Passkey
|
label: Passkey
|
||||||
tooltip: Copy the passkey from the /rss page
|
help: "Copy the passkey from the /rss page."
|
||||||
description: "Copy the passkey from the /rss page."
|
|
||||||
|
|
||||||
irc:
|
irc:
|
||||||
network: SuperBits
|
network: SuperBits
|
||||||
server: irc.superbits.org:6697
|
server: irc.superbits.org
|
||||||
port: 6697
|
port: 6697
|
||||||
|
tls: true
|
||||||
channels:
|
channels:
|
||||||
- "#autodl"
|
- "#autodl"
|
||||||
announcers:
|
announcers:
|
||||||
- SuperBits
|
- 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:
|
parse:
|
||||||
type: single
|
type: single
|
||||||
lines:
|
lines:
|
||||||
-
|
- test:
|
||||||
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]"
|
||||||
- "-[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[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]"
|
||||||
- "-[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]"
|
|
||||||
pattern: '\-\[(.*)\]2\[(.*)\]3\[(\d+)\]4\[Size\:\s(.*)\]5\[FL\:\s(no|yes)\]6\[Scene\:\s(no|yes)\](?:7\[Pred\s(.*)\sago\])?'
|
pattern: '\-\[(.*)\]2\[(.*)\]3\[(\d+)\]4\[Size\:\s(.*)\]5\[FL\:\s(no|yes)\]6\[Scene\:\s(no|yes)\](?:7\[Pred\s(.*)\sago\])?'
|
||||||
vars:
|
vars:
|
||||||
- category
|
- category
|
||||||
|
|
|
@ -14,28 +14,38 @@ supports:
|
||||||
source: custom
|
source: custom
|
||||||
settings:
|
settings:
|
||||||
- name: rsskey
|
- name: rsskey
|
||||||
type: text
|
type: secret
|
||||||
label: RSS key
|
label: RSS key
|
||||||
tooltip: The rsskey in your TorrentLeech RSS feed link.
|
help: "Go to your profile and copy your RSS key"
|
||||||
description: "Go to your profile and copy and paste your RSS link to extract the rsskey."
|
|
||||||
regex: /([\da-fA-F]{20})
|
regex: /([\da-fA-F]{20})
|
||||||
|
|
||||||
irc:
|
irc:
|
||||||
network: TorrentLeech.org
|
network: TorrentLeech.org
|
||||||
server: irc.torrentleech.org:7021
|
server: irc.torrentleech.org
|
||||||
port: 7021
|
port: 7021
|
||||||
|
tls: true
|
||||||
channels:
|
channels:
|
||||||
- "#tlannounces"
|
- "#tlannounces"
|
||||||
announcers:
|
announcers:
|
||||||
- _AnnounceBot_
|
- _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:
|
parse:
|
||||||
type: single
|
type: single
|
||||||
lines:
|
lines:
|
||||||
-
|
- test:
|
||||||
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' - 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/000000"
|
||||||
- "New Torrent Announcement: <PC :: Iso> Name:'debian live 10 6 0 amd64 standard iso' uploaded by 'Anonymous' freeleech - http://www.tracker01.test/torrent/263302"
|
|
||||||
pattern: New Torrent Announcement:\s*<([^>]*)>\s*Name:'(.*)' uploaded by '([^']*)'\s*(freeleech)*\s*-\s*https?\:\/\/([^\/]+\/)torrent\/(\d+)
|
pattern: New Torrent Announcement:\s*<([^>]*)>\s*Name:'(.*)' uploaded by '([^']*)'\s*(freeleech)*\s*-\s*https?\:\/\/([^\/]+\/)torrent\/(\d+)
|
||||||
vars:
|
vars:
|
||||||
- category
|
- category
|
||||||
|
|
|
@ -13,33 +13,48 @@ supports:
|
||||||
- rss
|
- rss
|
||||||
source: gazelle
|
source: gazelle
|
||||||
settings:
|
settings:
|
||||||
- name: authkey
|
- name: authkey
|
||||||
type: text
|
type: secret
|
||||||
label: Auth key
|
label: Auth key
|
||||||
tooltip: Right click DL on a torrent and get the authkey.
|
help: Right click DL on a torrent and get the authkey.
|
||||||
description: Right click DL on a torrent and get the authkey.
|
- name: torrent_pass
|
||||||
- name: torrent_pass
|
type: secret
|
||||||
type: text
|
label: Torrent pass
|
||||||
label: Torrent pass
|
help: Right click DL on a torrent and get the 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.
|
|
||||||
|
|
||||||
irc:
|
irc:
|
||||||
network: P2P-Network
|
network: P2P-Network
|
||||||
server: irc.p2p-network.net:6697
|
server: irc.p2p-network.net
|
||||||
port: 6697
|
port: 6697
|
||||||
|
tls: true
|
||||||
channels:
|
channels:
|
||||||
- "#UHD.Announce"
|
- "#UHD.Announce"
|
||||||
announcers:
|
announcers:
|
||||||
- UHDBot
|
- UHDBot
|
||||||
- cr0nusbot
|
- 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:
|
parse:
|
||||||
type: single
|
type: single
|
||||||
lines:
|
lines:
|
||||||
-
|
- test:
|
||||||
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"
|
||||||
- "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"
|
|
||||||
pattern: 'New Torrent: (.*) Type: (.*?) Freeleech: (.*) Size: (.*) - https?:\/\/.* \/ (https?:\/\/.*id=\d+)'
|
pattern: 'New Torrent: (.*) Type: (.*?) Freeleech: (.*) Size: (.*) - https?:\/\/.* \/ (https?:\/\/.*id=\d+)'
|
||||||
vars:
|
vars:
|
||||||
- torrentName
|
- torrentName
|
||||||
|
|
|
@ -5,9 +5,8 @@ import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"github.com/autobrr/autobrr/internal/domain"
|
"github.com/autobrr/autobrr/internal/domain"
|
||||||
)
|
)
|
||||||
|
@ -26,9 +25,15 @@ type Service interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
repo domain.IndexerRepo
|
repo domain.IndexerRepo
|
||||||
indexerDefinitions map[string]domain.IndexerDefinition
|
|
||||||
indexerInstances map[string]domain.IndexerDefinition
|
// 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
|
mapIndexerIRCToName map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +49,14 @@ func NewService(repo domain.IndexerRepo) Service {
|
||||||
func (s *service) Store(indexer domain.Indexer) (*domain.Indexer, error) {
|
func (s *service) Store(indexer domain.Indexer) (*domain.Indexer, error) {
|
||||||
i, err := s.repo.Store(indexer)
|
i, err := s.repo.Store(indexer)
|
||||||
if err != nil {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +69,13 @@ func (s *service) Update(indexer domain.Indexer) (*domain.Indexer, error) {
|
||||||
return nil, err
|
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
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,46 +114,60 @@ func (s *service) GetAll() ([]*domain.IndexerDefinition, error) {
|
||||||
var res = make([]*domain.IndexerDefinition, 0)
|
var res = make([]*domain.IndexerDefinition, 0)
|
||||||
|
|
||||||
for _, indexer := range indexers {
|
for _, indexer := range indexers {
|
||||||
in := s.getDefinitionByName(indexer.Identifier)
|
indexerDefinition, err := s.mapIndexer(indexer)
|
||||||
if in == nil {
|
if err != nil {
|
||||||
// if no indexerDefinition found, continue
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
temp := domain.IndexerDefinition{
|
if indexerDefinition == nil {
|
||||||
ID: indexer.ID,
|
continue
|
||||||
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
|
res = append(res, indexerDefinition)
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
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) {
|
func (s *service) GetTemplates() ([]domain.IndexerDefinition, error) {
|
||||||
|
|
||||||
definitions := s.indexerDefinitions
|
definitions := s.indexerDefinitions
|
||||||
|
@ -152,44 +186,81 @@ func (s *service) Start() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
indexers, err := s.GetAll()
|
indexerDefinitions, err := s.GetAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, indexer := range indexers {
|
for _, indexerDefinition := range indexerDefinitions {
|
||||||
if !indexer.Enabled {
|
s.indexerInstances[indexerDefinition.Identifier] = *indexerDefinition
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
s.indexerInstances[indexer.Identifier] = *indexer
|
s.mapIRCIndexerLookup(indexerDefinition.Identifier, *indexerDefinition)
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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
|
// LoadIndexerDefinitions load definitions from golang embed fs
|
||||||
func (s *service) LoadIndexerDefinitions() error {
|
func (s *service) LoadIndexerDefinitions() error {
|
||||||
|
|
||||||
entries, err := fs.ReadDir(Definitions, "definitions")
|
entries, err := fs.ReadDir(Definitions, "definitions")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Msgf("failed reading directory: %s", err)
|
log.Fatal().Stack().Msgf("failed reading directory: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(entries) == 0 {
|
if len(entries) == 0 {
|
||||||
log.Fatal().Msgf("failed reading directory: %s", err)
|
log.Fatal().Stack().Msgf("failed reading directory: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,19 +268,19 @@ func (s *service) LoadIndexerDefinitions() error {
|
||||||
filePath := "definitions/" + f.Name()
|
filePath := "definitions/" + f.Name()
|
||||||
|
|
||||||
if strings.Contains(f.Name(), ".yaml") {
|
if strings.Contains(f.Name(), ".yaml") {
|
||||||
log.Debug().Msgf("parsing: %v", filePath)
|
log.Trace().Msgf("parsing: %v", filePath)
|
||||||
|
|
||||||
var d domain.IndexerDefinition
|
var d domain.IndexerDefinition
|
||||||
|
|
||||||
data, err := fs.ReadFile(Definitions, filePath)
|
data, err := fs.ReadFile(Definitions, filePath)
|
||||||
if err != nil {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = yaml.Unmarshal(data, &d)
|
err = yaml.Unmarshal(data, &d)
|
||||||
if err != nil {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,10 +288,13 @@ func (s *service) LoadIndexerDefinitions() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info().Msgf("Loaded %d indexer definitions", len(s.indexerDefinitions))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) GetIndexerByAnnounce(name string) *domain.IndexerDefinition {
|
func (s *service) GetIndexerByAnnounce(name string) *domain.IndexerDefinition {
|
||||||
|
name = strings.ToLower(name)
|
||||||
|
|
||||||
if identifier, idOk := s.mapIndexerIRCToName[name]; idOk {
|
if identifier, idOk := s.mapIndexerIRCToName[name]; idOk {
|
||||||
if indexer, ok := s.indexerInstances[identifier]; ok {
|
if indexer, ok := s.indexerInstances[identifier]; ok {
|
||||||
|
|
|
@ -25,14 +25,19 @@ type Handler struct {
|
||||||
network *domain.IrcNetwork
|
network *domain.IrcNetwork
|
||||||
announceService announce.Service
|
announceService announce.Service
|
||||||
|
|
||||||
|
client *irc.Client
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
stopped chan struct{}
|
stopped chan struct{}
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
|
||||||
|
lastPing time.Time
|
||||||
|
lastAnnounce time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandler(network domain.IrcNetwork, announceService announce.Service) *Handler {
|
func NewHandler(network domain.IrcNetwork, announceService announce.Service) *Handler {
|
||||||
return &Handler{
|
return &Handler{
|
||||||
|
client: nil,
|
||||||
conn: nil,
|
conn: nil,
|
||||||
ctx: nil,
|
ctx: nil,
|
||||||
stopped: make(chan struct{}),
|
stopped: make(chan struct{}),
|
||||||
|
@ -44,7 +49,7 @@ func NewHandler(network domain.IrcNetwork, announceService announce.Service) *Ha
|
||||||
func (s *Handler) Run() error {
|
func (s *Handler) Run() error {
|
||||||
//log.Debug().Msgf("server %+v", s.network)
|
//log.Debug().Msgf("server %+v", s.network)
|
||||||
|
|
||||||
if s.network.Addr == "" {
|
if s.network.Server == "" {
|
||||||
return errors.New("addr not set")
|
return errors.New("addr not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +64,7 @@ func (s *Handler) Run() error {
|
||||||
var netConn net.Conn
|
var netConn net.Conn
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
addr := s.network.Addr
|
addr := fmt.Sprintf("%v:%v", s.network.Server, s.network.Port)
|
||||||
|
|
||||||
// decide to use SSL or not
|
// decide to use SSL or not
|
||||||
if s.network.TLS {
|
if s.network.TLS {
|
||||||
|
@ -88,9 +93,9 @@ func (s *Handler) Run() error {
|
||||||
log.Info().Msgf("Connected to: %v", addr)
|
log.Info().Msgf("Connected to: %v", addr)
|
||||||
|
|
||||||
config := irc.ClientConfig{
|
config := irc.ClientConfig{
|
||||||
Nick: s.network.Nick,
|
Nick: s.network.NickServ.Account,
|
||||||
User: s.network.Nick,
|
User: s.network.NickServ.Account,
|
||||||
Name: s.network.Nick,
|
Name: s.network.NickServ.Account,
|
||||||
Pass: s.network.Pass,
|
Pass: s.network.Pass,
|
||||||
Handler: irc.HandlerFunc(func(c *irc.Client, m *irc.Message) {
|
Handler: irc.HandlerFunc(func(c *irc.Client, m *irc.Message) {
|
||||||
switch m.Command {
|
switch m.Command {
|
||||||
|
@ -101,38 +106,66 @@ func (s *Handler) Run() error {
|
||||||
log.Error().Msgf("error joining channels %v", err)
|
log.Error().Msgf("error joining channels %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "366":
|
// 322 TOPIC
|
||||||
// TODO: handle joined
|
// 333 UP
|
||||||
log.Debug().Msgf("JOINED: %v", m)
|
// 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":
|
case "433":
|
||||||
// TODO: handle nick in use
|
// 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
|
// 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":
|
case "KICK":
|
||||||
log.Debug().Msgf("KICK: %v", m)
|
log.Debug().Msgf("%v: KICK: %v", s.network.Server, m)
|
||||||
|
|
||||||
case "MODE":
|
case "MODE":
|
||||||
// TODO: handle mode change
|
err := s.handleMode(m)
|
||||||
log.Debug().Msgf("MODE CHANGE: %v", m)
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msgf("error MODE change: %v", m)
|
||||||
|
}
|
||||||
|
|
||||||
case "INVITE":
|
case "INVITE":
|
||||||
// TODO: handle invite
|
// TODO: handle invite
|
||||||
log.Debug().Msgf("INVITE: %v", m)
|
log.Debug().Msgf("%v: INVITE: %v", s.network.Server, m)
|
||||||
|
|
||||||
case "PART":
|
case "PART":
|
||||||
// TODO: handle parted
|
// TODO: handle parted
|
||||||
log.Debug().Msgf("PART: %v", m)
|
log.Debug().Msgf("%v: PART: %v", s.network.Server, m)
|
||||||
|
|
||||||
case "PRIVMSG":
|
case "PRIVMSG":
|
||||||
err := s.onMessage(m)
|
err := s.onMessage(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Msgf("error on message %v", err)
|
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
|
// Create the client
|
||||||
client := irc.NewClient(s.conn, config)
|
client := irc.NewClient(s.conn, config)
|
||||||
|
|
||||||
|
s.client = client
|
||||||
|
|
||||||
// Connect
|
// Connect
|
||||||
err = client.RunContext(ctx)
|
err = client.RunContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -157,9 +192,9 @@ func (s *Handler) GetNetwork() *domain.IrcNetwork {
|
||||||
func (s *Handler) Stop() {
|
func (s *Handler) Stop() {
|
||||||
s.cancel()
|
s.cancel()
|
||||||
|
|
||||||
//if !s.isStopped() {
|
if !s.isStopped() {
|
||||||
// close(s.stopped)
|
close(s.stopped)
|
||||||
//}
|
}
|
||||||
|
|
||||||
if s.conn != nil {
|
if s.conn != nil {
|
||||||
s.conn.Close()
|
s.conn.Close()
|
||||||
|
@ -176,41 +211,40 @@ func (s *Handler) isStopped() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Handler) onConnect(client *irc.Client, channels []domain.IrcChannel) error {
|
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 {
|
time.Sleep(2 * time.Second)
|
||||||
cmd := strings.TrimLeft(command, "/")
|
|
||||||
|
|
||||||
log.Info().Msgf("send connect command: %v to network: %s", cmd, s.network.Name)
|
if s.network.NickServ.Password != "" {
|
||||||
|
err := s.handleNickServPRIVMSG(s.network.NickServ.Account, s.network.NickServ.Password)
|
||||||
err := client.Write(cmd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msgf("error sending connect command %v to network: %v", command, s.network.Name)
|
log.Error().Err(err).Msgf("error nickserv: %v", s.network.Name)
|
||||||
continue
|
return err
|
||||||
//return err
|
|
||||||
}
|
}
|
||||||
|
identified = true
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ch := range channels {
|
time.Sleep(3 * time.Second)
|
||||||
myChan := fmt.Sprintf("JOIN %s", ch.Name)
|
|
||||||
|
|
||||||
// handle channel password
|
if s.network.InviteCommand != "" {
|
||||||
if ch.Password != "" {
|
|
||||||
myChan = fmt.Sprintf("JOIN %s %s", ch.Name, ch.Password)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := client.Write(myChan)
|
err := s.handleInvitePRIVMSG(s.network.InviteCommand)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msgf("error joining channel: %v", ch.Name)
|
log.Error().Err(err).Msgf("error sending connect command %v to network: %v", s.network.InviteCommand, s.network.Name)
|
||||||
continue
|
return err
|
||||||
//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
|
return nil
|
||||||
|
@ -221,7 +255,12 @@ func (s *Handler) OnJoin(msg string) (interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Handler) onMessage(msg *irc.Message) 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
|
// parse announce
|
||||||
channel := &msg.Params[0]
|
channel := &msg.Params[0]
|
||||||
|
@ -231,10 +270,14 @@ func (s *Handler) onMessage(msg *irc.Message) error {
|
||||||
|
|
||||||
// add correlationID and tracing
|
// 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
|
// clean message
|
||||||
cleanedMsg := cleanMessage(message)
|
cleanedMsg := cleanMessage(message)
|
||||||
|
log.Debug().Msgf("%v: %v %v: %v", s.network.Server, *channel, *announcer, cleanedMsg)
|
||||||
|
|
||||||
|
s.lastAnnounce = time.Now()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err := s.announceService.Parse(announceID, cleanedMsg)
|
err := s.announceService.Parse(announceID, cleanedMsg)
|
||||||
|
@ -246,6 +289,145 @@ func (s *Handler) onMessage(msg *irc.Message) error {
|
||||||
return nil
|
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
|
// irc line can contain lots of extra stuff like color so lets clean that
|
||||||
func cleanMessage(message string) string {
|
func cleanMessage(message string) string {
|
||||||
var regexMessageClean = `\x0f|\x1f|\x02|\x03(?:[\d]{1,2}(?:,[\d]{1,2})?)?`
|
var regexMessageClean = `\x0f|\x1f|\x02|\x03(?:[\d]{1,2}(?:,[\d]{1,2})?)?`
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
StartHandlers()
|
StartHandlers()
|
||||||
|
StopHandlers()
|
||||||
StopNetwork(name string) error
|
StopNetwork(name string) error
|
||||||
ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error)
|
ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error)
|
||||||
GetNetworkByID(id int64) (*domain.IrcNetwork, error)
|
GetNetworkByID(id int64) (*domain.IrcNetwork, error)
|
||||||
|
@ -56,7 +57,7 @@ func (s *service) StartHandlers() {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
channels, err := s.repo.ListChannels(network.ID)
|
channels, err := s.repo.ListChannels(network.ID)
|
||||||
if err != nil {
|
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
|
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 {
|
func (s *service) startNetwork(network domain.IrcNetwork) error {
|
||||||
// look if we have the network in handlers already, if so start it
|
// look if we have the network in handlers already, if so start it
|
||||||
if handler, found := s.handlers[network.Name]; found {
|
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)
|
channels, err := s.repo.ListChannels(network.ID)
|
||||||
if err != nil {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
network.Channels = append(network.Channels, channels...)
|
network.Channels = append(network.Channels, channels...)
|
||||||
|
@ -154,7 +164,7 @@ func (s *service) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error)
|
||||||
for _, n := range networks {
|
for _, n := range networks {
|
||||||
channels, err := s.repo.ListChannels(n.ID)
|
channels, err := s.repo.ListChannels(n.ID)
|
||||||
if err != nil {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
n.Channels = append(n.Channels, channels...)
|
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 {
|
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
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -191,24 +211,40 @@ func (s *service) StoreNetwork(network *domain.IrcNetwork) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop or start network
|
// stop or start network
|
||||||
if !network.Enabled {
|
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)
|
err := s.startNetwork(*network)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msgf("could not start network: %+v", network.Name)
|
log.Error().Err(err).Msgf("could not start network: %+v", network.Name)
|
||||||
return fmt.Errorf("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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ func Setup(cfg domain.Config) {
|
||||||
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
||||||
case "WARN":
|
case "WARN":
|
||||||
zerolog.SetGlobalLevel(zerolog.WarnLevel)
|
zerolog.SetGlobalLevel(zerolog.WarnLevel)
|
||||||
|
case "TRACE":
|
||||||
|
zerolog.SetGlobalLevel(zerolog.TraceLevel)
|
||||||
default:
|
default:
|
||||||
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ func NewService(actionService action.Service) Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) Process(announce domain.Announce) error {
|
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 {
|
if announce.Filter.Actions == nil {
|
||||||
return fmt.Errorf("no actions for filter: %v", announce.Filter.Name)
|
return fmt.Errorf("no actions for filter: %v", announce.Filter.Name)
|
||||||
|
|
|
@ -41,3 +41,10 @@ func (s *Server) Start() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) Shutdown() {
|
||||||
|
log.Info().Msg("Shutting down server")
|
||||||
|
|
||||||
|
// stop all irc handlers
|
||||||
|
s.ircService.StopHandlers()
|
||||||
|
}
|
||||||
|
|
|
@ -7,10 +7,11 @@ interface Props {
|
||||||
name: string;
|
name: string;
|
||||||
label: string;
|
label: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
defaultValue?: boolean;
|
||||||
className?: string;
|
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">
|
<ul className="mt-2 divide-y divide-gray-200">
|
||||||
<Switch.Group as="li" className="py-4 flex items-center justify-between">
|
<Switch.Group as="li" className="py-4 flex items-center justify-between">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
|
@ -27,6 +28,7 @@ const SwitchGroup: React.FC<Props> = ({name, label, description}) => (
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
name={name}
|
name={name}
|
||||||
|
defaultValue={defaultValue as any}
|
||||||
render={({input: {onChange, checked, value}}) => (
|
render={({input: {onChange, checked, value}}) => (
|
||||||
<Switch
|
<Switch
|
||||||
value={value}
|
value={value}
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
import {Field} from "react-final-form";
|
import { Field } from "react-final-form";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Error from "./Error";
|
import Error from "./Error";
|
||||||
import {classNames} from "../../styles/utils";
|
import { classNames } from "../../styles/utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name: string;
|
name: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
help?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
defaultValue?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TextFieldWide: React.FC<Props> = ({name, label, placeholder, required, className}) => (
|
const TextFieldWide: React.FC<Props> = ({ name, label, help, placeholder, defaultValue, required, hidden, className}) => (
|
||||||
<div
|
<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">
|
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>
|
<div>
|
||||||
|
|
||||||
|
@ -23,17 +26,22 @@ const TextFieldWide: React.FC<Props> = ({name, label, placeholder, required, cla
|
||||||
<div className="sm:col-span-2">
|
<div className="sm:col-span-2">
|
||||||
<Field
|
<Field
|
||||||
name={name}
|
name={name}
|
||||||
render={({input, meta}) => (
|
defaultValue={defaultValue}
|
||||||
|
render={({ input, meta }) => (
|
||||||
<input
|
<input
|
||||||
{...input}
|
{...input}
|
||||||
id={name}
|
id={name}
|
||||||
type="text"
|
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")}
|
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}
|
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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,15 +7,19 @@ interface Props {
|
||||||
name: string;
|
name: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
defaultValue?: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NumberFieldWide: React.FC<Props> = ({
|
const NumberFieldWide: React.FC<Props> = ({
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
defaultValue,
|
||||||
required,
|
required,
|
||||||
|
hidden,
|
||||||
className,
|
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">
|
<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">
|
<div className="sm:col-span-2">
|
||||||
<Field
|
<Field
|
||||||
name={name}
|
name={name}
|
||||||
|
defaultValue={defaultValue}
|
||||||
parse={(v) => v & parseInt(v, 10)}
|
parse={(v) => v & parseInt(v, 10)}
|
||||||
render={({ input, meta }) => (
|
render={({ input, meta }) => (
|
||||||
<input
|
<input
|
||||||
|
|
56
web/src/components/inputs/wide/PasswordField.tsx
Normal file
56
web/src/components/inputs/wide/PasswordField.tsx
Normal 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;
|
|
@ -1,3 +1,4 @@
|
||||||
export { default as NumberFieldWide } from "./NumberField";
|
export { default as NumberFieldWide } from "./NumberField";
|
||||||
|
export { default as PasswordFieldWide } from "./PasswordField";
|
||||||
export { default as RadioFieldsetWide } from "./RadioFieldsetWide";
|
export { default as RadioFieldsetWide } from "./RadioFieldsetWide";
|
||||||
export { default as SelectFieldWide } from "./SelectField";
|
export { default as SelectFieldWide } from "./SelectField";
|
||||||
|
|
|
@ -31,6 +31,43 @@ export interface Indexer {
|
||||||
settings: object | any;
|
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 {
|
export interface Filter {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -87,16 +124,29 @@ export interface DownloadClient {
|
||||||
settings: object;
|
settings: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NickServ {
|
||||||
|
account: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Network {
|
export interface Network {
|
||||||
id: number;
|
id?: number;
|
||||||
name: string;
|
name: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
addr: string;
|
server: string;
|
||||||
nick: string;
|
port: number;
|
||||||
username: string;
|
tls: boolean;
|
||||||
realname: string;
|
invite_command: string;
|
||||||
pass: string;
|
nickserv: {
|
||||||
sasl: SASL;
|
account: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
channels: Channel[];
|
||||||
|
settings: object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Channel {
|
||||||
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SASL {
|
export interface SASL {
|
||||||
|
|
|
@ -1,23 +1,24 @@
|
||||||
import React, {Fragment} from "react";
|
import React, { Fragment } from "react";
|
||||||
import {useMutation, useQuery} from "react-query";
|
import { useMutation, useQuery } from "react-query";
|
||||||
import {Indexer} from "../../domain/interfaces";
|
import { Channel, Indexer, IndexerSchema, IndexerSchemaSettings, Network } from "../../domain/interfaces";
|
||||||
import {sleep} from "../../utils/utils";
|
import { sleep } from "../../utils/utils";
|
||||||
import {XIcon} from "@heroicons/react/solid";
|
import { XIcon } from "@heroicons/react/solid";
|
||||||
import {Dialog, Transition} from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import {Field, Form} from "react-final-form";
|
import { Field, Form } from "react-final-form";
|
||||||
import DEBUG from "../../components/debug";
|
import DEBUG from "../../components/debug";
|
||||||
import Select from "react-select";
|
import Select from "react-select";
|
||||||
import {queryClient} from "../../App";
|
import { queryClient } from "../../App";
|
||||||
import { SwitchGroup } from "../../components/inputs";
|
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
|
||||||
import APIClient from "../../api/APIClient";
|
import APIClient from "../../api/APIClient";
|
||||||
|
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
|
||||||
|
|
||||||
interface props {
|
interface props {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
toggle: any;
|
toggle: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
function IndexerAddForm({isOpen, toggle}: props) {
|
function IndexerAddForm({ isOpen, toggle }: props) {
|
||||||
const {data} = useQuery<any[], Error>('indexerSchema', APIClient.indexers.getSchema,
|
const { data } = useQuery<IndexerSchema[], Error>('indexerSchema', APIClient.indexers.getSchema,
|
||||||
{
|
{
|
||||||
enabled: isOpen,
|
enabled: isOpen,
|
||||||
refetchOnWindowFocus: false
|
refetchOnWindowFocus: false
|
||||||
|
@ -33,59 +34,122 @@ function IndexerAddForm({isOpen, toggle}: props) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = (data: any) => {
|
const ircMutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
|
||||||
mutation.mutate(data)
|
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) => {
|
const renderSettingFields = (indexer: string) => {
|
||||||
if (indexer !== "") {
|
if (indexer !== "") {
|
||||||
// let ind = data.find(i => i.implementation_name === indexer)
|
|
||||||
let ind = data && data.find(i => i.identifier === indexer)
|
let ind = data && data.find(i => i.identifier === indexer)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key="opt">
|
<div key="opt">
|
||||||
{ind && ind.settings && ind.settings.map((f: any, idx: number) => {
|
{ind && ind.settings && ind.settings.map((f: any, idx: number) => {
|
||||||
switch (f.type) {
|
switch (f.type) {
|
||||||
case "text":
|
case "text":
|
||||||
return (
|
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}>
|
<TextFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} defaultValue=""/>
|
||||||
<div>
|
)
|
||||||
<label
|
case "secret":
|
||||||
htmlFor={f.name}
|
return (
|
||||||
className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2"
|
<PasswordFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} defaultValue="" />
|
||||||
>
|
)
|
||||||
{f.label}
|
}
|
||||||
</label>
|
})}
|
||||||
</div>
|
<div hidden={true}>
|
||||||
<div className="sm:col-span-2">
|
<TextFieldWide name={`name`} label="Name" defaultValue={ind?.name} />
|
||||||
<Field name={"settings."+f.name}>
|
</div>
|
||||||
{({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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</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 (
|
return (
|
||||||
<Transition.Root show={isOpen} as={Fragment}>
|
<Transition.Root show={isOpen} as={Fragment}>
|
||||||
<Dialog as="div" static className="fixed inset-0 overflow-hidden" open={isOpen} onClose={toggle}>
|
<Dialog as="div" static className="fixed inset-0 overflow-hidden" open={isOpen} onClose={toggle}>
|
||||||
<div className="absolute inset-0 overflow-hidden">
|
<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">
|
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
|
@ -100,18 +164,17 @@ function IndexerAddForm({isOpen, toggle}: props) {
|
||||||
<div className="w-screen max-w-2xl">
|
<div className="w-screen max-w-2xl">
|
||||||
<Form
|
<Form
|
||||||
initialValues={{
|
initialValues={{
|
||||||
name: "",
|
|
||||||
enabled: true,
|
enabled: true,
|
||||||
identifier: "",
|
identifier: "",
|
||||||
|
irc: {}
|
||||||
}}
|
}}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
{({handleSubmit, values}) => {
|
{({ handleSubmit, values }) => {
|
||||||
return (
|
return (
|
||||||
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
|
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
|
||||||
onSubmit={handleSubmit}>
|
onSubmit={handleSubmit}>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
{/* Header */}
|
|
||||||
<div className="px-4 py-6 bg-gray-50 sm:px-6">
|
<div className="px-4 py-6 bg-gray-50 sm:px-6">
|
||||||
<div className="flex items-start justify-between space-x-3">
|
<div className="flex items-start justify-between space-x-3">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
|
@ -129,43 +192,14 @@ function IndexerAddForm({isOpen, toggle}: props) {
|
||||||
onClick={toggle}
|
onClick={toggle}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Close panel</span>
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Divider container */}
|
|
||||||
<div
|
<div
|
||||||
className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
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
|
<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">
|
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"
|
name="identifier"
|
||||||
parse={val => val && val.value}
|
parse={val => val && val.value}
|
||||||
format={val => data && data.find((o: any) => o.value === val)}
|
format={val => data && data.find((o: any) => o.value === val)}
|
||||||
render={({input, meta}) => (
|
render={({ input, meta }) => (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Select {...input}
|
<Select {...input}
|
||||||
isClearable={true}
|
isClearable={true}
|
||||||
placeholder="Choose an indexer"
|
placeholder="Choose an indexer"
|
||||||
options={data && data.sort((a,b): any => a.name.localeCompare(b.name)).map(v => ({
|
|
||||||
label: v.name,
|
options={data && data.sort((a, b): any => a.name.localeCompare(b.name)).map(v => ({
|
||||||
value: v.identifier
|
label: v.name,
|
||||||
// value: v.implementation_name
|
value: v.identifier
|
||||||
}))}/>
|
}))} />
|
||||||
{/*<Error name={input.name} classNames="text-red mt-2 block" />*/}
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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)}
|
{renderSettingFields(values.identifier)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{renderIrcSettingFields(values.identifier)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -223,7 +263,7 @@ function IndexerAddForm({isOpen, toggle}: props) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DEBUG values={values}/>
|
<DEBUG values={values} />
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import {Fragment, useRef} from "react";
|
import { Fragment, useRef } from "react";
|
||||||
import {useMutation } from "react-query";
|
import { useMutation } from "react-query";
|
||||||
import {Indexer} from "../../domain/interfaces";
|
import { Indexer } from "../../domain/interfaces";
|
||||||
import {sleep} from "../../utils/utils";
|
import { sleep } from "../../utils/utils";
|
||||||
import {ExclamationIcon, XIcon} from "@heroicons/react/solid";
|
import { ExclamationIcon, XIcon } from "@heroicons/react/solid";
|
||||||
import {Dialog, Transition} from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import {Field, Form} from "react-final-form";
|
import { Field, Form } from "react-final-form";
|
||||||
import DEBUG from "../../components/debug";
|
import DEBUG from "../../components/debug";
|
||||||
import { SwitchGroup } from "../../components/inputs";
|
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
|
||||||
import {useToggle} from "../../hooks/hooks";
|
import { useToggle } from "../../hooks/hooks";
|
||||||
import APIClient from "../../api/APIClient";
|
import APIClient from "../../api/APIClient";
|
||||||
import {queryClient} from "../../App";
|
import { queryClient } from "../../App";
|
||||||
|
import { PasswordFieldWide } from "../../components/inputs/wide";
|
||||||
|
|
||||||
interface props {
|
interface props {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
@ -17,7 +18,7 @@ interface props {
|
||||||
indexer: Indexer;
|
indexer: Indexer;
|
||||||
}
|
}
|
||||||
|
|
||||||
function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
function IndexerUpdateForm({ isOpen, toggle, indexer }: props) {
|
||||||
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false)
|
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false)
|
||||||
|
|
||||||
const mutation = useMutation((indexer: Indexer) => APIClient.indexers.update(indexer), {
|
const mutation = useMutation((indexer: Indexer) => APIClient.indexers.update(indexer), {
|
||||||
|
@ -55,31 +56,11 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
||||||
switch (f.type) {
|
switch (f.type) {
|
||||||
case "text":
|
case "text":
|
||||||
return (
|
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}>
|
<TextFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} />
|
||||||
<div>
|
)
|
||||||
<label
|
case "secret":
|
||||||
htmlFor={f.name}
|
return (
|
||||||
className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2"
|
<PasswordFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} />
|
||||||
>
|
|
||||||
{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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
|
@ -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 (
|
return (
|
||||||
<Transition.Root show={isOpen} as={Fragment}>
|
<Transition.Root show={isOpen} as={Fragment}>
|
||||||
<Dialog as="div" static className="fixed inset-0 overflow-hidden" open={isOpen} onClose={toggle}>
|
<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. */}
|
{/* 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">
|
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
|
||||||
​
|
​
|
||||||
</span>
|
</span>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
|
@ -172,7 +150,7 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Transition.Root>
|
</Transition.Root>
|
||||||
<div className="absolute inset-0 overflow-hidden">
|
<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">
|
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
|
@ -195,12 +173,11 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
||||||
}}
|
}}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
{({handleSubmit, values}) => {
|
{({ handleSubmit, values }) => {
|
||||||
return (
|
return (
|
||||||
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
|
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
|
||||||
onSubmit={handleSubmit}>
|
onSubmit={handleSubmit}>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
{/* Header */}
|
|
||||||
<div className="px-4 py-6 bg-gray-50 sm:px-6">
|
<div className="px-4 py-6 bg-gray-50 sm:px-6">
|
||||||
<div className="flex items-start justify-between space-x-3">
|
<div className="flex items-start justify-between space-x-3">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
|
@ -218,13 +195,12 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
||||||
onClick={toggle}
|
onClick={toggle}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Close panel</span>
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Divider container */}
|
|
||||||
<div
|
<div
|
||||||
className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||||
<div
|
<div
|
||||||
|
@ -238,7 +214,7 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<Field name="name">
|
<Field name="name">
|
||||||
{({input, meta}) => (
|
{({ input, meta }) => (
|
||||||
<div className="sm:col-span-2">
|
<div className="sm:col-span-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
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"
|
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 &&
|
{meta.touched && meta.error &&
|
||||||
<span>{meta.error}</span>}
|
<span>{meta.error}</span>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
@ -289,7 +265,7 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DEBUG values={values}/>
|
<DEBUG values={values} />
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -5,26 +5,14 @@ import {Dialog, Transition} from "@headlessui/react";
|
||||||
import {XIcon} from "@heroicons/react/solid";
|
import {XIcon} from "@heroicons/react/solid";
|
||||||
import {Field, Form} from "react-final-form";
|
import {Field, Form} from "react-final-form";
|
||||||
import DEBUG from "../../components/debug";
|
import DEBUG from "../../components/debug";
|
||||||
import {SwitchGroup, TextAreaWide, TextFieldWide} from "../../components/inputs";
|
import {SwitchGroup, TextFieldWide} from "../../components/inputs";
|
||||||
import {queryClient} from "../../App";
|
import {queryClient} from "../../App";
|
||||||
|
|
||||||
import arrayMutators from "final-form-arrays";
|
import arrayMutators from "final-form-arrays";
|
||||||
import { FieldArray } from "react-final-form-arrays";
|
import { FieldArray } from "react-final-form-arrays";
|
||||||
import {classNames} from "../../styles/utils";
|
import {classNames} from "../../styles/utils";
|
||||||
import APIClient from "../../api/APIClient";
|
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 IrcNetworkAddForm({isOpen, toggle}: any) {
|
function IrcNetworkAddForm({isOpen, toggle}: any) {
|
||||||
const mutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
|
const mutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
|
||||||
|
@ -53,12 +41,8 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
|
||||||
errors.name = "Required";
|
errors.name = "Required";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!values.addr) {
|
if (!values.server) {
|
||||||
errors.addr = "Required";
|
errors.server = "Required";
|
||||||
}
|
|
||||||
|
|
||||||
if (!values.nick) {
|
|
||||||
errors.nick = "Required";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
|
@ -86,18 +70,9 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
|
||||||
initialValues={{
|
initialValues={{
|
||||||
name: "",
|
name: "",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
addr: "",
|
server: "",
|
||||||
tls: false,
|
tls: false,
|
||||||
nick: "",
|
|
||||||
pass: "",
|
pass: "",
|
||||||
// connect_commands: "",
|
|
||||||
// sasl: {
|
|
||||||
// mechanism: "",
|
|
||||||
// plain: {
|
|
||||||
// username: "",
|
|
||||||
// password: "",
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
}}
|
}}
|
||||||
mutators={{
|
mutators={{
|
||||||
...arrayMutators
|
...arrayMutators
|
||||||
|
@ -110,7 +85,6 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
|
||||||
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
|
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
|
||||||
onSubmit={handleSubmit}>
|
onSubmit={handleSubmit}>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
{/* Header */}
|
|
||||||
<div className="px-4 py-6 bg-gray-50 sm:px-6">
|
<div className="px-4 py-6 bg-gray-50 sm:px-6">
|
||||||
<div className="flex items-start justify-between space-x-3">
|
<div className="flex items-start justify-between space-x-3">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
|
@ -144,88 +118,19 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<TextFieldWide name="server" label="Server" placeholder="Address: Eg irc.server.net" required={true} />
|
||||||
<TextFieldWide name="addr" label="Address" placeholder="Address:port eg irc.server.net:6697" 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">
|
<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>
|
</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" />
|
<PasswordFieldWide name="invite_command" label="Invite command" />
|
||||||
|
|
||||||
|
|
||||||
{/* <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>*/}
|
|
||||||
{/* )} />*/}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -295,7 +200,6 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={pristine || invalid}
|
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")}
|
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
|
Create
|
||||||
|
|
|
@ -1,34 +1,22 @@
|
||||||
import {Fragment, useEffect, useRef} from "react";
|
import { Fragment, useEffect, useRef } from "react";
|
||||||
import {useMutation} from "react-query";
|
import { useMutation } from "react-query";
|
||||||
import {Network} from "../../domain/interfaces";
|
import { Network } from "../../domain/interfaces";
|
||||||
import {Dialog, Transition} from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import {XIcon} from "@heroicons/react/solid";
|
import { XIcon } from "@heroicons/react/solid";
|
||||||
import {Field, Form} from "react-final-form";
|
import { Field, Form } from "react-final-form";
|
||||||
import DEBUG from "../../components/debug";
|
import DEBUG from "../../components/debug";
|
||||||
import {SwitchGroup, TextAreaWide, TextFieldWide} from "../../components/inputs";
|
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
|
||||||
import {queryClient} from "../../App";
|
import { queryClient } from "../../App";
|
||||||
|
|
||||||
import arrayMutators from "final-form-arrays";
|
import arrayMutators from "final-form-arrays";
|
||||||
import { FieldArray } from "react-final-form-arrays";
|
import { FieldArray } from "react-final-form-arrays";
|
||||||
import {classNames} from "../../styles/utils";
|
import { classNames } from "../../styles/utils";
|
||||||
import {useToggle} from "../../hooks/hooks";
|
import { useToggle } from "../../hooks/hooks";
|
||||||
import {DeleteModal} from "../../components/modals";
|
import { DeleteModal } from "../../components/modals";
|
||||||
import APIClient from "../../api/APIClient";
|
import APIClient from "../../api/APIClient";
|
||||||
|
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
|
||||||
|
|
||||||
|
function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
|
||||||
// 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) {
|
|
||||||
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false)
|
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false)
|
||||||
|
|
||||||
const mutation = useMutation((network: Network) => APIClient.irc.updateNetwork(network), {
|
const mutation = useMutation((network: Network) => APIClient.irc.updateNetwork(network), {
|
||||||
|
@ -70,12 +58,16 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
||||||
errors.name = "Required";
|
errors.name = "Required";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!values.addr) {
|
if (!values.server) {
|
||||||
errors.addr = "Required";
|
errors.server = "Required";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!values.nick) {
|
if (!values.port) {
|
||||||
errors.nick = "Required";
|
errors.port = "Required";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!values.nickserv.account) {
|
||||||
|
errors.nickserv.account = "Required";
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
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."
|
text="Are you sure you want to remove this network and channels? This action cannot be undone."
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 overflow-hidden">
|
<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">
|
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
|
@ -117,19 +109,13 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
||||||
id: network.id,
|
id: network.id,
|
||||||
name: network.name,
|
name: network.name,
|
||||||
enabled: network.enabled,
|
enabled: network.enabled,
|
||||||
addr: network.addr,
|
server: network.server,
|
||||||
|
port: network.port,
|
||||||
tls: network.tls,
|
tls: network.tls,
|
||||||
nick: network.nick,
|
nickserv: network.nickserv,
|
||||||
pass: network.pass,
|
pass: network.pass,
|
||||||
|
invite_command: network.invite_command,
|
||||||
connect_commands: network.connect_commands,
|
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
|
channels: network.channels
|
||||||
}}
|
}}
|
||||||
mutators={{
|
mutators={{
|
||||||
|
@ -138,10 +124,10 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
||||||
validate={validate}
|
validate={validate}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
{({handleSubmit, values, pristine, invalid}) => {
|
{({ handleSubmit, values, pristine, invalid }) => {
|
||||||
return (
|
return (
|
||||||
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
|
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
|
||||||
onSubmit={handleSubmit}>
|
onSubmit={handleSubmit}>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="px-4 py-6 bg-gray-50 sm:px-6">
|
<div className="px-4 py-6 bg-gray-50 sm:px-6">
|
||||||
|
@ -160,7 +146,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
||||||
onClick={toggle}
|
onClick={toggle}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Close panel</span>
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -172,94 +158,36 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<div className="px-6 space-y-1 mt-6">
|
||||||
<TextFieldWide name="addr" label="Address" placeholder="Address:port eg irc.server.net:6697" required={true} />
|
<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">
|
<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>
|
</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" />
|
||||||
|
|
||||||
|
<PasswordFieldWide name="invite_command" label="Invite command" />
|
||||||
{/* <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>*/}
|
|
||||||
{/* )} />*/}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -294,7 +222,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
||||||
onClick={() => fields.remove(index)}
|
onClick={() => fields.remove(index)}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Remove</span>
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
@ -337,7 +265,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={pristine || invalid}
|
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
|
Save
|
||||||
</button>
|
</button>
|
||||||
|
@ -345,27 +273,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/*<div*/}
|
<DEBUG values={values} />
|
||||||
{/* 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}/>
|
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -82,7 +82,7 @@ function IrcSettings() {
|
||||||
scope="col"
|
scope="col"
|
||||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||||
>
|
>
|
||||||
Addr
|
Server
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
|
@ -138,8 +138,8 @@ const ListItem = ({ idx, network }: any) => {
|
||||||
</Switch>
|
</Switch>
|
||||||
</td>
|
</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 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.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.nick}</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">
|
<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}>
|
<span className="text-indigo-600 hover:text-indigo-900 cursor-pointer" onClick={toggleUpdate}>
|
||||||
Edit
|
Edit
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue