mirror of
https://github.com/idanoo/autobrr
synced 2025-07-22 16:29:12 +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
|
||||
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM)
|
||||
|
||||
if err := srv.Start(); err != nil {
|
||||
log.Fatal().Err(err).Msg("could not start server")
|
||||
log.Fatal().Stack().Err(err).Msg("could not start server")
|
||||
return
|
||||
}
|
||||
|
||||
for sig := range sigCh {
|
||||
switch sig {
|
||||
case syscall.SIGHUP:
|
||||
log.Print("shutting down server")
|
||||
log.Print("shutting down server sighup")
|
||||
srv.Shutdown()
|
||||
os.Exit(1)
|
||||
case syscall.SIGINT, syscall.SIGTERM:
|
||||
log.Print("shutting down server")
|
||||
//srv.Shutdown()
|
||||
case syscall.SIGINT, syscall.SIGQUIT:
|
||||
srv.Shutdown()
|
||||
os.Exit(1)
|
||||
case syscall.SIGKILL, syscall.SIGTERM:
|
||||
srv.Shutdown()
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ port = 8989
|
|||
#
|
||||
# Options: "ERROR", "DEBUG", "INFO", "WARN"
|
||||
#
|
||||
logLevel = "DEBUG"
|
||||
logLevel = "TRACE"
|
||||
|
||||
# Session secret
|
||||
#
|
||||
|
|
|
@ -26,12 +26,15 @@ func (r *IndexerRepo) Store(indexer domain.Indexer) (*domain.Indexer, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.db.Exec(`INSERT INTO indexer (enabled, name, identifier, settings) VALUES (?, ?, ?, ?)`, indexer.Enabled, indexer.Name, indexer.Identifier, settings)
|
||||
res, err := r.db.Exec(`INSERT INTO indexer (enabled, name, identifier, settings) VALUES (?, ?, ?, ?)`, indexer.Enabled, indexer.Name, indexer.Identifier, settings)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("error executing query")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, _ := res.LastInsertId()
|
||||
indexer.ID = id
|
||||
|
||||
return &indexer, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ package database
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strings"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
|
||||
|
@ -24,7 +23,7 @@ func (ir *IrcRepo) Store(announce domain.Announce) error {
|
|||
|
||||
func (ir *IrcRepo) GetNetworkByID(id int64) (*domain.IrcNetwork, error) {
|
||||
|
||||
row := ir.db.QueryRow("SELECT id, enabled, name, addr, tls, nick, pass, connect_commands, sasl_mechanism, sasl_plain_username, sasl_plain_password FROM irc_network WHERE id = ?", id)
|
||||
row := ir.db.QueryRow("SELECT id, enabled, name, server, port, tls, pass, invite_command, nickserv_account, nickserv_password FROM irc_network WHERE id = ?", id)
|
||||
if err := row.Err(); err != nil {
|
||||
log.Fatal().Err(err)
|
||||
return nil, err
|
||||
|
@ -32,22 +31,19 @@ func (ir *IrcRepo) GetNetworkByID(id int64) (*domain.IrcNetwork, error) {
|
|||
|
||||
var n domain.IrcNetwork
|
||||
|
||||
var pass, connectCommands sql.NullString
|
||||
var saslMechanism, saslPlainUsername, saslPlainPassword sql.NullString
|
||||
var pass, inviteCmd sql.NullString
|
||||
var nsAccount, nsPassword sql.NullString
|
||||
var tls sql.NullBool
|
||||
|
||||
if err := row.Scan(&n.ID, &n.Enabled, &n.Name, &n.Addr, &tls, &n.Nick, &pass, &connectCommands, &saslMechanism, &saslPlainUsername, &saslPlainPassword); err != nil {
|
||||
if err := row.Scan(&n.ID, &n.Enabled, &n.Name, &n.Server, &n.Port, &tls, &pass, &inviteCmd, &nsAccount, &nsPassword); err != nil {
|
||||
log.Fatal().Err(err)
|
||||
}
|
||||
|
||||
n.TLS = tls.Bool
|
||||
n.Pass = pass.String
|
||||
if connectCommands.Valid {
|
||||
n.ConnectCommands = strings.Split(connectCommands.String, "\r\n")
|
||||
}
|
||||
n.SASL.Mechanism = saslMechanism.String
|
||||
n.SASL.Plain.Username = saslPlainUsername.String
|
||||
n.SASL.Plain.Password = saslPlainPassword.String
|
||||
n.InviteCommand = inviteCmd.String
|
||||
n.NickServ.Account = nsAccount.String
|
||||
n.NickServ.Password = nsPassword.String
|
||||
|
||||
return &n, nil
|
||||
}
|
||||
|
@ -84,7 +80,7 @@ func (ir *IrcRepo) DeleteNetwork(ctx context.Context, id int64) error {
|
|||
|
||||
func (ir *IrcRepo) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) {
|
||||
|
||||
rows, err := ir.db.QueryContext(ctx, "SELECT id, enabled, name, addr, tls, nick, pass, connect_commands FROM irc_network")
|
||||
rows, err := ir.db.QueryContext(ctx, "SELECT id, enabled, name, server, port, tls, pass, invite_command, nickserv_account, nickserv_password FROM irc_network")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err)
|
||||
}
|
||||
|
@ -95,20 +91,16 @@ func (ir *IrcRepo) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error
|
|||
for rows.Next() {
|
||||
var net domain.IrcNetwork
|
||||
|
||||
//var username, realname, pass, connectCommands sql.NullString
|
||||
var pass, connectCommands sql.NullString
|
||||
var pass, inviteCmd sql.NullString
|
||||
var tls sql.NullBool
|
||||
|
||||
if err := rows.Scan(&net.ID, &net.Enabled, &net.Name, &net.Addr, &tls, &net.Nick, &pass, &connectCommands); err != nil {
|
||||
if err := rows.Scan(&net.ID, &net.Enabled, &net.Name, &net.Server, &net.Port, &tls, &pass, &inviteCmd, &net.NickServ.Account, &net.NickServ.Password); err != nil {
|
||||
log.Fatal().Err(err)
|
||||
}
|
||||
|
||||
net.TLS = tls.Bool
|
||||
net.Pass = pass.String
|
||||
|
||||
if connectCommands.Valid {
|
||||
net.ConnectCommands = strings.Split(connectCommands.String, "\r\n")
|
||||
}
|
||||
net.InviteCommand = inviteCmd.String
|
||||
|
||||
networks = append(networks, net)
|
||||
}
|
||||
|
@ -131,7 +123,6 @@ func (ir *IrcRepo) ListChannels(networkID int64) ([]domain.IrcChannel, error) {
|
|||
for rows.Next() {
|
||||
var ch domain.IrcChannel
|
||||
|
||||
//if err := rows.Scan(&ch.ID, &ch.Name, &ch.Enabled, &ch.Pass, &ch.InviteCommand, &ch.InviteHTTPURL, &ch.InviteHTTPHeader, &ch.InviteHTTPData); err != nil {
|
||||
if err := rows.Scan(&ch.ID, &ch.Name, &ch.Enabled); err != nil {
|
||||
log.Fatal().Err(err)
|
||||
}
|
||||
|
@ -149,20 +140,10 @@ func (ir *IrcRepo) StoreNetwork(network *domain.IrcNetwork) error {
|
|||
|
||||
netName := toNullString(network.Name)
|
||||
pass := toNullString(network.Pass)
|
||||
connectCommands := toNullString(strings.Join(network.ConnectCommands, "\r\n"))
|
||||
inviteCmd := toNullString(network.InviteCommand)
|
||||
|
||||
var saslMechanism, saslPlainUsername, saslPlainPassword sql.NullString
|
||||
if network.SASL.Mechanism != "" {
|
||||
saslMechanism = toNullString(network.SASL.Mechanism)
|
||||
switch network.SASL.Mechanism {
|
||||
case "PLAIN":
|
||||
saslPlainUsername = toNullString(network.SASL.Plain.Username)
|
||||
saslPlainPassword = toNullString(network.SASL.Plain.Password)
|
||||
default:
|
||||
log.Warn().Msgf("unsupported SASL mechanism: %q", network.SASL.Mechanism)
|
||||
//return fmt.Errorf("cannot store network: unsupported SASL mechanism %q", network.SASL.Mechanism)
|
||||
}
|
||||
}
|
||||
nsAccount := toNullString(network.NickServ.Account)
|
||||
nsPassword := toNullString(network.NickServ.Password)
|
||||
|
||||
var err error
|
||||
if network.ID != 0 {
|
||||
|
@ -170,26 +151,24 @@ func (ir *IrcRepo) StoreNetwork(network *domain.IrcNetwork) error {
|
|||
_, err = ir.db.Exec(`UPDATE irc_network
|
||||
SET enabled = ?,
|
||||
name = ?,
|
||||
addr = ?,
|
||||
server = ?,
|
||||
port = ?,
|
||||
tls = ?,
|
||||
nick = ?,
|
||||
pass = ?,
|
||||
connect_commands = ?,
|
||||
sasl_mechanism = ?,
|
||||
sasl_plain_username = ?,
|
||||
sasl_plain_password = ?,
|
||||
invite_command = ?,
|
||||
nickserv_account = ?,
|
||||
nickserv_password = ?,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?`,
|
||||
network.Enabled,
|
||||
netName,
|
||||
network.Addr,
|
||||
network.Server,
|
||||
network.Port,
|
||||
network.TLS,
|
||||
network.Nick,
|
||||
pass,
|
||||
connectCommands,
|
||||
saslMechanism,
|
||||
saslPlainUsername,
|
||||
saslPlainPassword,
|
||||
inviteCmd,
|
||||
nsAccount,
|
||||
nsPassword,
|
||||
network.ID,
|
||||
)
|
||||
} else {
|
||||
|
@ -198,25 +177,23 @@ func (ir *IrcRepo) StoreNetwork(network *domain.IrcNetwork) error {
|
|||
res, err = ir.db.Exec(`INSERT INTO irc_network (
|
||||
enabled,
|
||||
name,
|
||||
addr,
|
||||
server,
|
||||
port,
|
||||
tls,
|
||||
nick,
|
||||
pass,
|
||||
connect_commands,
|
||||
sasl_mechanism,
|
||||
sasl_plain_username,
|
||||
sasl_plain_password
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
invite_command,
|
||||
nickserv_account,
|
||||
nickserv_password
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
network.Enabled,
|
||||
netName,
|
||||
network.Addr,
|
||||
network.Server,
|
||||
network.Port,
|
||||
network.TLS,
|
||||
network.Nick,
|
||||
pass,
|
||||
connectCommands,
|
||||
saslMechanism,
|
||||
saslPlainUsername,
|
||||
saslPlainPassword,
|
||||
inviteCmd,
|
||||
nsAccount,
|
||||
nsPassword,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("error executing query")
|
||||
|
|
|
@ -24,7 +24,8 @@ CREATE TABLE indexer
|
|||
name TEXT NOT NULL,
|
||||
settings TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE (identifier)
|
||||
);
|
||||
|
||||
CREATE TABLE irc_network
|
||||
|
@ -32,29 +33,30 @@ CREATE TABLE irc_network
|
|||
id INTEGER PRIMARY KEY,
|
||||
enabled BOOLEAN,
|
||||
name TEXT NOT NULL,
|
||||
addr TEXT NOT NULL,
|
||||
nick TEXT NOT NULL,
|
||||
server TEXT NOT NULL,
|
||||
port INTEGER NOT NULL,
|
||||
tls BOOLEAN,
|
||||
pass TEXT,
|
||||
connect_commands TEXT,
|
||||
sasl_mechanism TEXT,
|
||||
sasl_plain_username TEXT,
|
||||
sasl_plain_password TEXT,
|
||||
invite_command TEXT,
|
||||
nickserv_account TEXT,
|
||||
nickserv_password TEXT,
|
||||
connected BOOLEAN,
|
||||
connected_since TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
unique (addr, nick)
|
||||
UNIQUE (server, port, nickserv_account)
|
||||
);
|
||||
|
||||
CREATE TABLE irc_channel
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
enabled BOOLEAN,
|
||||
name TEXT NOT NULL,
|
||||
password TEXT,
|
||||
detached BOOLEAN,
|
||||
id INTEGER PRIMARY KEY,
|
||||
enabled BOOLEAN,
|
||||
name TEXT NOT NULL,
|
||||
password TEXT,
|
||||
detached BOOLEAN,
|
||||
network_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (network_id) REFERENCES irc_network(id),
|
||||
unique (network_id, name)
|
||||
UNIQUE (network_id, name)
|
||||
);
|
||||
|
||||
CREATE TABLE filter
|
||||
|
|
|
@ -9,7 +9,7 @@ type IndexerRepo interface {
|
|||
}
|
||||
|
||||
type Indexer struct {
|
||||
ID int `json:"id"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Identifier string `json:"identifier"`
|
||||
Enabled bool `json:"enabled"`
|
||||
|
@ -39,15 +39,21 @@ type IndexerSetting struct {
|
|||
Type string `json:"type"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Label string `json:"label"`
|
||||
Description string `json:"description"`
|
||||
Default string `json:"default,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Help string `json:"help,omitempty"`
|
||||
Regex string `json:"regex,omitempty"`
|
||||
}
|
||||
|
||||
type IndexerIRC struct {
|
||||
Network string
|
||||
Server string
|
||||
Channels []string
|
||||
Announcers []string
|
||||
Network string `json:"network"`
|
||||
Server string `json:"server"`
|
||||
Port int `json:"port"`
|
||||
TLS bool `json:"tls"`
|
||||
Channels []string `json:"channels"`
|
||||
Announcers []string `json:"announcers"`
|
||||
SettingsMap map[string]string `json:"-"`
|
||||
Settings []IndexerSetting `json:"settings"`
|
||||
}
|
||||
|
||||
type IndexerParse struct {
|
||||
|
|
|
@ -1,35 +1,37 @@
|
|||
package domain
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type IrcChannel struct {
|
||||
ID int64 `json:"id"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Detached bool `json:"detached"`
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
ID int64 `json:"id"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
Detached bool `json:"detached"`
|
||||
Monitoring bool `json:"monitoring"`
|
||||
}
|
||||
|
||||
type SASL struct {
|
||||
Mechanism string `json:"mechanism,omitempty"`
|
||||
|
||||
Plain struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
} `json:"plain,omitempty"`
|
||||
type NickServ struct {
|
||||
Account string `json:"account,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
type IrcNetwork struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Addr string `json:"addr"`
|
||||
TLS bool `json:"tls"`
|
||||
Nick string `json:"nick"`
|
||||
Pass string `json:"pass"`
|
||||
ConnectCommands []string `json:"connect_commands"`
|
||||
SASL SASL `json:"sasl,omitempty"`
|
||||
Channels []IrcChannel `json:"channels"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Server string `json:"server"`
|
||||
Port int `json:"port"`
|
||||
TLS bool `json:"tls"`
|
||||
Pass string `json:"pass"`
|
||||
InviteCommand string `json:"invite_command"`
|
||||
NickServ NickServ `json:"nickserv,omitempty"`
|
||||
Channels []IrcChannel `json:"channels"`
|
||||
Connected bool `json:"connected"`
|
||||
ConnectedSince *time.Time `json:"connected_since"`
|
||||
}
|
||||
|
||||
type IrcRepo interface {
|
||||
|
|
|
@ -183,7 +183,7 @@ func (s *service) Update(filter domain.Filter) (*domain.Filter, error) {
|
|||
}
|
||||
|
||||
for _, i := range filter.Indexers {
|
||||
if err = s.repo.StoreIndexerConnection(f.ID, i.ID); err != nil {
|
||||
if err = s.repo.StoreIndexerConnection(f.ID, int(i.ID)); err != nil {
|
||||
log.Error().Err(err).Msgf("could not store filter indexer connections: %v", filter.Name)
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package http
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
|
@ -17,97 +18,96 @@ type actionService interface {
|
|||
}
|
||||
|
||||
type actionHandler struct {
|
||||
encoder encoder
|
||||
actionService actionService
|
||||
encoder encoder
|
||||
service actionService
|
||||
}
|
||||
|
||||
func newActionHandler(encoder encoder, service actionService) *actionHandler {
|
||||
return &actionHandler{
|
||||
encoder: encoder,
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h actionHandler) Routes(r chi.Router) {
|
||||
r.Get("/", h.getActions)
|
||||
r.Post("/", h.storeAction)
|
||||
r.Delete("/{actionID}", h.deleteAction)
|
||||
r.Put("/{actionID}", h.updateAction)
|
||||
r.Patch("/{actionID}/toggleEnabled", h.toggleActionEnabled)
|
||||
r.Delete("/{id}", h.deleteAction)
|
||||
r.Put("/{id}", h.updateAction)
|
||||
r.Patch("/{id}/toggleEnabled", h.toggleActionEnabled)
|
||||
}
|
||||
|
||||
func (h actionHandler) getActions(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
actions, err := h.actionService.Fetch()
|
||||
actions, err := h.service.Fetch()
|
||||
if err != nil {
|
||||
// encode error
|
||||
}
|
||||
|
||||
h.encoder.StatusResponse(ctx, w, actions, http.StatusOK)
|
||||
h.encoder.StatusResponse(r.Context(), w, actions, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h actionHandler) storeAction(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
data domain.Action
|
||||
)
|
||||
var data domain.Action
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
// encode error
|
||||
return
|
||||
}
|
||||
|
||||
action, err := h.actionService.Store(data)
|
||||
action, err := h.service.Store(data)
|
||||
if err != nil {
|
||||
// encode error
|
||||
}
|
||||
|
||||
h.encoder.StatusResponse(ctx, w, action, http.StatusCreated)
|
||||
h.encoder.StatusResponse(r.Context(), w, action, http.StatusCreated)
|
||||
}
|
||||
|
||||
func (h actionHandler) updateAction(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
data domain.Action
|
||||
)
|
||||
var data domain.Action
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
// encode error
|
||||
return
|
||||
}
|
||||
|
||||
action, err := h.actionService.Store(data)
|
||||
action, err := h.service.Store(data)
|
||||
if err != nil {
|
||||
// encode error
|
||||
}
|
||||
|
||||
h.encoder.StatusResponse(ctx, w, action, http.StatusCreated)
|
||||
h.encoder.StatusResponse(r.Context(), w, action, http.StatusCreated)
|
||||
}
|
||||
|
||||
func (h actionHandler) deleteAction(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
actionID = chi.URLParam(r, "actionID")
|
||||
)
|
||||
actionID, err := parseInt(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
h.encoder.StatusResponse(r.Context(), w, errors.New("bad param id"), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
// if !actionID return error
|
||||
|
||||
id, _ := strconv.Atoi(actionID)
|
||||
|
||||
if err := h.actionService.Delete(id); err != nil {
|
||||
if err := h.service.Delete(actionID); err != nil {
|
||||
// encode error
|
||||
}
|
||||
|
||||
h.encoder.StatusResponse(ctx, w, nil, http.StatusNoContent)
|
||||
h.encoder.StatusResponse(r.Context(), w, nil, http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h actionHandler) toggleActionEnabled(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
actionID = chi.URLParam(r, "actionID")
|
||||
)
|
||||
actionID, err := parseInt(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
h.encoder.StatusResponse(r.Context(), w, errors.New("bad param id"), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
// if !actionID return error
|
||||
|
||||
id, _ := strconv.Atoi(actionID)
|
||||
|
||||
if err := h.actionService.ToggleEnabled(id); err != nil {
|
||||
if err := h.service.ToggleEnabled(actionID); err != nil {
|
||||
// encode error
|
||||
}
|
||||
|
||||
h.encoder.StatusResponse(ctx, w, nil, http.StatusCreated)
|
||||
h.encoder.StatusResponse(r.Context(), w, nil, http.StatusCreated)
|
||||
}
|
||||
|
||||
func parseInt(s string) (int, error) {
|
||||
u, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(u), nil
|
||||
}
|
||||
|
|
|
@ -16,8 +16,15 @@ type authService interface {
|
|||
}
|
||||
|
||||
type authHandler struct {
|
||||
encoder encoder
|
||||
authService authService
|
||||
encoder encoder
|
||||
service authService
|
||||
}
|
||||
|
||||
func newAuthHandler(encoder encoder, service authService) *authHandler {
|
||||
return &authHandler{
|
||||
encoder: encoder,
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -49,7 +56,7 @@ func (h authHandler) login(w http.ResponseWriter, r *http.Request) {
|
|||
store.Options.SameSite = http.SameSiteStrictMode
|
||||
session, _ := store.Get(r, "user_session")
|
||||
|
||||
_, err := h.authService.Login(data.Username, data.Password)
|
||||
_, err := h.service.Login(data.Username, data.Password)
|
||||
if err != nil {
|
||||
h.encoder.StatusResponse(ctx, w, nil, http.StatusUnauthorized)
|
||||
return
|
||||
|
|
|
@ -20,6 +20,10 @@ type configHandler struct {
|
|||
encoder encoder
|
||||
}
|
||||
|
||||
func newConfigHandler(encoder encoder) *configHandler {
|
||||
return &configHandler{encoder: encoder}
|
||||
}
|
||||
|
||||
func (h configHandler) Routes(r chi.Router) {
|
||||
r.Get("/", h.getConfig)
|
||||
}
|
||||
|
|
|
@ -18,8 +18,15 @@ type downloadClientService interface {
|
|||
}
|
||||
|
||||
type downloadClientHandler struct {
|
||||
encoder encoder
|
||||
downloadClientService downloadClientService
|
||||
encoder encoder
|
||||
service downloadClientService
|
||||
}
|
||||
|
||||
func newDownloadClientHandler(encoder encoder, service downloadClientService) *downloadClientHandler {
|
||||
return &downloadClientHandler{
|
||||
encoder: encoder,
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h downloadClientHandler) Routes(r chi.Router) {
|
||||
|
@ -33,7 +40,7 @@ func (h downloadClientHandler) Routes(r chi.Router) {
|
|||
func (h downloadClientHandler) listDownloadClients(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
clients, err := h.downloadClientService.List()
|
||||
clients, err := h.service.List()
|
||||
if err != nil {
|
||||
//
|
||||
}
|
||||
|
@ -52,7 +59,7 @@ func (h downloadClientHandler) store(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
client, err := h.downloadClientService.Store(data)
|
||||
client, err := h.service.Store(data)
|
||||
if err != nil {
|
||||
// encode error
|
||||
}
|
||||
|
@ -72,7 +79,7 @@ func (h downloadClientHandler) test(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
err := h.downloadClientService.Test(data)
|
||||
err := h.service.Test(data)
|
||||
if err != nil {
|
||||
// encode error
|
||||
h.encoder.StatusResponse(ctx, w, nil, http.StatusBadRequest)
|
||||
|
@ -93,7 +100,7 @@ func (h downloadClientHandler) update(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
client, err := h.downloadClientService.Store(data)
|
||||
client, err := h.service.Store(data)
|
||||
if err != nil {
|
||||
// encode error
|
||||
}
|
||||
|
@ -111,7 +118,7 @@ func (h downloadClientHandler) delete(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
id, _ := strconv.Atoi(clientID)
|
||||
|
||||
if err := h.downloadClientService.Delete(id); err != nil {
|
||||
if err := h.service.Delete(id); err != nil {
|
||||
// encode error
|
||||
}
|
||||
|
||||
|
|
|
@ -20,8 +20,15 @@ type filterService interface {
|
|||
}
|
||||
|
||||
type filterHandler struct {
|
||||
encoder encoder
|
||||
filterService filterService
|
||||
encoder encoder
|
||||
service filterService
|
||||
}
|
||||
|
||||
func newFilterHandler(encoder encoder, service filterService) *filterHandler {
|
||||
return &filterHandler{
|
||||
encoder: encoder,
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h filterHandler) Routes(r chi.Router) {
|
||||
|
@ -35,7 +42,7 @@ func (h filterHandler) Routes(r chi.Router) {
|
|||
func (h filterHandler) getFilters(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
trackers, err := h.filterService.ListFilters()
|
||||
trackers, err := h.service.ListFilters()
|
||||
if err != nil {
|
||||
//
|
||||
}
|
||||
|
@ -51,7 +58,7 @@ func (h filterHandler) getByID(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
id, _ := strconv.Atoi(filterID)
|
||||
|
||||
filter, err := h.filterService.FindByID(id)
|
||||
filter, err := h.service.FindByID(id)
|
||||
if err != nil {
|
||||
h.encoder.StatusNotFound(ctx, w)
|
||||
return
|
||||
|
@ -68,7 +75,7 @@ func (h filterHandler) storeFilterAction(w http.ResponseWriter, r *http.Request)
|
|||
|
||||
id, _ := strconv.Atoi(filterID)
|
||||
|
||||
filter, err := h.filterService.FindByID(id)
|
||||
filter, err := h.service.FindByID(id)
|
||||
if err != nil {
|
||||
//
|
||||
}
|
||||
|
@ -87,7 +94,7 @@ func (h filterHandler) store(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
filter, err := h.filterService.Store(data)
|
||||
filter, err := h.service.Store(data)
|
||||
if err != nil {
|
||||
// encode error
|
||||
return
|
||||
|
@ -107,7 +114,7 @@ func (h filterHandler) update(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
filter, err := h.filterService.Update(data)
|
||||
filter, err := h.service.Update(data)
|
||||
if err != nil {
|
||||
// encode error
|
||||
return
|
||||
|
@ -124,7 +131,7 @@ func (h filterHandler) delete(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
id, _ := strconv.Atoi(filterID)
|
||||
|
||||
if err := h.filterService.Delete(id); err != nil {
|
||||
if err := h.service.Delete(id); err != nil {
|
||||
// return err
|
||||
}
|
||||
|
||||
|
|
|
@ -20,8 +20,17 @@ type indexerService interface {
|
|||
}
|
||||
|
||||
type indexerHandler struct {
|
||||
encoder encoder
|
||||
indexerService indexerService
|
||||
encoder encoder
|
||||
service indexerService
|
||||
ircSvc ircService
|
||||
}
|
||||
|
||||
func newIndexerHandler(encoder encoder, service indexerService, ircSvc ircService) *indexerHandler {
|
||||
return &indexerHandler{
|
||||
encoder: encoder,
|
||||
service: service,
|
||||
ircSvc: ircSvc,
|
||||
}
|
||||
}
|
||||
|
||||
func (h indexerHandler) Routes(r chi.Router) {
|
||||
|
@ -36,7 +45,7 @@ func (h indexerHandler) Routes(r chi.Router) {
|
|||
func (h indexerHandler) getSchema(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
indexers, err := h.indexerService.GetTemplates()
|
||||
indexers, err := h.service.GetTemplates()
|
||||
if err != nil {
|
||||
//
|
||||
}
|
||||
|
@ -45,21 +54,20 @@ func (h indexerHandler) getSchema(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func (h indexerHandler) store(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
data domain.Indexer
|
||||
)
|
||||
var data domain.Indexer
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
indexer, err := h.indexerService.Store(data)
|
||||
indexer, err := h.service.Store(data)
|
||||
if err != nil {
|
||||
//
|
||||
h.encoder.StatusResponse(r.Context(), w, nil, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
h.encoder.StatusResponse(ctx, w, indexer, http.StatusCreated)
|
||||
h.encoder.StatusResponse(r.Context(), w, indexer, http.StatusCreated)
|
||||
}
|
||||
|
||||
func (h indexerHandler) update(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -72,7 +80,7 @@ func (h indexerHandler) update(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
indexer, err := h.indexerService.Update(data)
|
||||
indexer, err := h.service.Update(data)
|
||||
if err != nil {
|
||||
//
|
||||
}
|
||||
|
@ -88,7 +96,7 @@ func (h indexerHandler) delete(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
id, _ := strconv.Atoi(idParam)
|
||||
|
||||
if err := h.indexerService.Delete(id); err != nil {
|
||||
if err := h.service.Delete(id); err != nil {
|
||||
// return err
|
||||
}
|
||||
|
||||
|
@ -98,7 +106,7 @@ func (h indexerHandler) delete(w http.ResponseWriter, r *http.Request) {
|
|||
func (h indexerHandler) getAll(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
indexers, err := h.indexerService.GetAll()
|
||||
indexers, err := h.service.GetAll()
|
||||
if err != nil {
|
||||
//
|
||||
}
|
||||
|
@ -109,7 +117,7 @@ func (h indexerHandler) getAll(w http.ResponseWriter, r *http.Request) {
|
|||
func (h indexerHandler) list(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
indexers, err := h.indexerService.List()
|
||||
indexers, err := h.service.List()
|
||||
if err != nil {
|
||||
//
|
||||
}
|
||||
|
|
|
@ -21,8 +21,15 @@ type ircService interface {
|
|||
}
|
||||
|
||||
type ircHandler struct {
|
||||
encoder encoder
|
||||
ircService ircService
|
||||
encoder encoder
|
||||
service ircService
|
||||
}
|
||||
|
||||
func newIrcHandler(encoder encoder, service ircService) *ircHandler {
|
||||
return &ircHandler{
|
||||
encoder: encoder,
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h ircHandler) Routes(r chi.Router) {
|
||||
|
@ -38,7 +45,7 @@ func (h ircHandler) Routes(r chi.Router) {
|
|||
func (h ircHandler) listNetworks(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
networks, err := h.ircService.ListNetworks(ctx)
|
||||
networks, err := h.service.ListNetworks(ctx)
|
||||
if err != nil {
|
||||
//
|
||||
}
|
||||
|
@ -54,7 +61,7 @@ func (h ircHandler) getNetworkByID(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
id, _ := strconv.Atoi(networkID)
|
||||
|
||||
network, err := h.ircService.GetNetworkByID(int64(id))
|
||||
network, err := h.service.GetNetworkByID(int64(id))
|
||||
if err != nil {
|
||||
//
|
||||
}
|
||||
|
@ -72,9 +79,11 @@ func (h ircHandler) storeNetwork(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
err := h.ircService.StoreNetwork(&data)
|
||||
err := h.service.StoreNetwork(&data)
|
||||
if err != nil {
|
||||
//
|
||||
h.encoder.StatusResponse(ctx, w, nil, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
h.encoder.StatusResponse(ctx, w, nil, http.StatusCreated)
|
||||
|
@ -93,7 +102,7 @@ func (h ircHandler) storeChannel(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
err := h.ircService.StoreChannel(int64(id), &data)
|
||||
err := h.service.StoreChannel(int64(id), &data)
|
||||
if err != nil {
|
||||
//
|
||||
}
|
||||
|
@ -107,7 +116,7 @@ func (h ircHandler) stopNetwork(w http.ResponseWriter, r *http.Request) {
|
|||
networkID = chi.URLParam(r, "networkID")
|
||||
)
|
||||
|
||||
err := h.ircService.StopNetwork(networkID)
|
||||
err := h.service.StopNetwork(networkID)
|
||||
if err != nil {
|
||||
//
|
||||
}
|
||||
|
@ -123,7 +132,7 @@ func (h ircHandler) deleteNetwork(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
id, _ := strconv.Atoi(networkID)
|
||||
|
||||
err := h.ircService.DeleteNetwork(ctx, int64(id))
|
||||
err := h.service.DeleteNetwork(ctx, int64(id))
|
||||
if err != nil {
|
||||
//
|
||||
}
|
||||
|
|
|
@ -64,56 +64,19 @@ func (s Server) Handler() http.Handler {
|
|||
fileSystem.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
authHandler := authHandler{
|
||||
encoder: encoder,
|
||||
authService: s.authService,
|
||||
}
|
||||
|
||||
r.Route("/api/auth", authHandler.Routes)
|
||||
r.Route("/api/auth", newAuthHandler(encoder, s.authService).Routes)
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(IsAuthenticated)
|
||||
|
||||
actionHandler := actionHandler{
|
||||
encoder: encoder,
|
||||
actionService: s.actionService,
|
||||
}
|
||||
|
||||
r.Route("/api/actions", actionHandler.Routes)
|
||||
|
||||
downloadClientHandler := downloadClientHandler{
|
||||
encoder: encoder,
|
||||
downloadClientService: s.downloadClientService,
|
||||
}
|
||||
|
||||
r.Route("/api/download_clients", downloadClientHandler.Routes)
|
||||
|
||||
filterHandler := filterHandler{
|
||||
encoder: encoder,
|
||||
filterService: s.filterService,
|
||||
}
|
||||
|
||||
r.Route("/api/filters", filterHandler.Routes)
|
||||
|
||||
ircHandler := ircHandler{
|
||||
encoder: encoder,
|
||||
ircService: s.ircService,
|
||||
}
|
||||
|
||||
r.Route("/api/irc", ircHandler.Routes)
|
||||
|
||||
indexerHandler := indexerHandler{
|
||||
encoder: encoder,
|
||||
indexerService: s.indexerService,
|
||||
}
|
||||
|
||||
r.Route("/api/indexer", indexerHandler.Routes)
|
||||
|
||||
configHandler := configHandler{
|
||||
encoder: encoder,
|
||||
}
|
||||
|
||||
r.Route("/api/config", configHandler.Routes)
|
||||
r.Route("/api", func(r chi.Router) {
|
||||
r.Route("/actions", newActionHandler(encoder, s.actionService).Routes)
|
||||
r.Route("/config", newConfigHandler(encoder).Routes)
|
||||
r.Route("/download_clients", newDownloadClientHandler(encoder, s.downloadClientService).Routes)
|
||||
r.Route("/filters", newFilterHandler(encoder, s.filterService).Routes)
|
||||
r.Route("/irc", newIrcHandler(encoder, s.ircService).Routes)
|
||||
r.Route("/indexer", newIndexerHandler(encoder, s.indexerService, s.ircService).Routes)
|
||||
})
|
||||
})
|
||||
|
||||
//r.HandleFunc("/*", handler.ServeHTTP)
|
|
@ -14,31 +14,46 @@ supports:
|
|||
source: gazelle
|
||||
settings:
|
||||
- name: authkey
|
||||
type: text
|
||||
type: secret
|
||||
label: Auth key
|
||||
tooltip: Right click DL on a torrent and get the authkey.
|
||||
description: Right click DL on a torrent and get the authkey.
|
||||
help: Right click DL on a torrent and get the authkey.
|
||||
- name: torrent_pass
|
||||
type: text
|
||||
type: secret
|
||||
label: Torrent pass
|
||||
tooltip: Right click DL on a torrent and get the torrent_pass.
|
||||
description: Right click DL on a torrent and get the torrent_pass.
|
||||
help: Right click DL on a torrent and get the torrent_pass.
|
||||
|
||||
irc:
|
||||
network: AlphaRatio
|
||||
server: irc.alpharatio.cc:6697
|
||||
server: irc.alpharatio.cc
|
||||
port: 6697
|
||||
tls: true
|
||||
channels:
|
||||
- "#Announce"
|
||||
announcers:
|
||||
- Voyager
|
||||
settings:
|
||||
- name: nickserv.account
|
||||
type: text
|
||||
required: true
|
||||
label: NickServ Account
|
||||
help: NickServ account. Make sure to group your user and bot. Eg. user-bot
|
||||
- name: nickserv.password
|
||||
type: secret
|
||||
required: true
|
||||
label: NickServ Password
|
||||
help: NickServ password
|
||||
- name: invite_command
|
||||
type: secret
|
||||
default: "Voyager autobot USERNAME IRCKey"
|
||||
required: false
|
||||
label: Invite command
|
||||
help: Invite auth with Voyager.
|
||||
|
||||
parse:
|
||||
type: multi
|
||||
lines:
|
||||
-
|
||||
test:
|
||||
- "[New Release]-[MovieHD]-[War.For.The.Planet.Of.The.Apes.2017.INTERNAL.1080p.BluRay.CRF.x264-SAPHiRE]-[URL]-[ https://alpharatio.cc/torrents.php?id=699463 ]-[ 699434 ]-[ Uploaded 2 Mins, 59 Secs after pre. ]"
|
||||
- test:
|
||||
- "[New Release]-[MovieHD]-[That.Movie.2017.INTERNAL.1080p.BluRay.CRF.x264-GROUP]-[URL]-[ https://alpharatio.cc/torrents.php?id=000000 ]-[ 000000 ]-[ Uploaded 2 Mins, 59 Secs after pre. ]"
|
||||
pattern: \[New Release\]-\[(.*)\]-\[(.*)\]-\[URL\]-\[ (https?://.*)id=\d+ \]-\[ (\d+) \](?:-\[ Uploaded (.*) after pre. ])?
|
||||
vars:
|
||||
- category
|
||||
|
@ -46,9 +61,8 @@ parse:
|
|||
- baseUrl
|
||||
- torrentId
|
||||
- preTime
|
||||
-
|
||||
test:
|
||||
- "[AutoDL]-[MovieHD]-[699434]-[ 1 | 10659 | 1 | 1 ]-[War.For.The.Planet.Of.The.Apes.2017.INTERNAL.1080p.BluRay.CRF.x264-SAPHiRE]"
|
||||
- test:
|
||||
- "[AutoDL]-[MovieHD]-[000000]-[ 1 | 10659 | 1 | 1 ]-[That.Movie.2017.INTERNAL.1080p.BluRay.CRF.x264-GROUP]"
|
||||
pattern: \[AutoDL\]-\[.*\]-\[.*\]-\[ ([01]) \| (\d+) \| ([01]) \| ([01]) \]-\[.+\]
|
||||
vars:
|
||||
- scene
|
||||
|
|
|
@ -13,28 +13,44 @@ supports:
|
|||
- rss
|
||||
source: UNIT3D (F3NIX)
|
||||
settings:
|
||||
- name: passkey
|
||||
type: text
|
||||
label: Passkey
|
||||
tooltip: The passkey in your BeyondHD RSS feed.
|
||||
description: "Go to your profile and copy and paste your RSS link to extract the rsskey."
|
||||
- name: rsskey
|
||||
type: secret
|
||||
label: RSS key
|
||||
help: "Go to your profile, My Security, RSS Key and copy RSS key."
|
||||
|
||||
irc:
|
||||
network: BeyondHD-IRC
|
||||
server: irc.beyond-hd.me:6697
|
||||
server: irc.beyond-hd.me
|
||||
port: 6697
|
||||
tls: true
|
||||
channels:
|
||||
- "#bhd_announce"
|
||||
announcers:
|
||||
- Willie
|
||||
- Millie
|
||||
settings:
|
||||
- name: nickserv.account
|
||||
type: text
|
||||
required: true
|
||||
label: NickServ Account
|
||||
help: NickServ account. Make sure to group your user and bot. Eg. user|autodl
|
||||
- name: nickserv.password
|
||||
type: secret
|
||||
required: true
|
||||
label: NickServ Password
|
||||
help: NickServ password
|
||||
- name: invite_command
|
||||
type: secret
|
||||
default: "Millie announce ircKey"
|
||||
required: true
|
||||
label: Invite command
|
||||
help: Invite auth with Millie.
|
||||
|
||||
parse:
|
||||
type: single
|
||||
lines:
|
||||
-
|
||||
test:
|
||||
- "New Torrent: Orange.Is.the.New.Black.S01.1080p.Blu-ray.AVC.DTS-HD.MA.5.1-Test Category: TV By: Uploader Size: 137.73 GB Link: https://beyond-hd.me/details.php?id=25918"
|
||||
- test:
|
||||
- "New Torrent: That.Show.S01.1080p.Blu-ray.AVC.DTS-HD.MA.5.1-Test Category: TV By: Uploader Size: 137.73 GB Link: https://beyond-hd.me/details.php?id=00000"
|
||||
pattern: 'New Torrent:(.*)Category:(.*)By:(.*)Size:(.*)Link: https?\:\/\/([^\/]+\/).*[&\?]id=(\d+)'
|
||||
vars:
|
||||
- torrentName
|
||||
|
|
|
@ -13,42 +13,57 @@ supports:
|
|||
- rss
|
||||
source: gazelle
|
||||
settings:
|
||||
- name: authkey
|
||||
type: text
|
||||
label: Auth key
|
||||
description: Right click DL on a torrent and get the authkey.
|
||||
- name: torrent_pass
|
||||
type: text
|
||||
label: Torrent pass
|
||||
description: Right click DL on a torrent and get the torrent_pass.
|
||||
- name: authkey
|
||||
type: secret
|
||||
label: Auth key
|
||||
help: Right click DL on a torrent and get the authkey.
|
||||
- name: torrent_pass
|
||||
type: secret
|
||||
label: Torrent pass
|
||||
help: Right click DL on a torrent and get the torrent_pass.
|
||||
|
||||
irc:
|
||||
network: BroadcasTheNet
|
||||
server: irc.broadcasthenet.net:6697
|
||||
server: irc.broadcasthenet.net
|
||||
port: 6697
|
||||
tls: true
|
||||
channels:
|
||||
- "#BTN-Announce"
|
||||
announcers:
|
||||
- Barney
|
||||
settings:
|
||||
- name: nickserv.account
|
||||
type: text
|
||||
required: true
|
||||
label: NickServ Account
|
||||
help: NickServ account. Make sure to group your user and bot. Eg. user|autodl
|
||||
- name: nickserv.password
|
||||
type: secret
|
||||
required: true
|
||||
label: NickServ Password
|
||||
help: NickServ password
|
||||
- name: invite_command
|
||||
type: secret
|
||||
default: "CableGuy IDENTIFY USERNAME IRCKey"
|
||||
required: true
|
||||
label: Invite command
|
||||
help: Invite auth with CableGuy.
|
||||
|
||||
parse:
|
||||
type: multi
|
||||
lines:
|
||||
-
|
||||
test:
|
||||
- "NOW BROADCASTING! [ Lost S06E07 720p WEB-DL DD 5.1 H.264 - LP ]"
|
||||
- test:
|
||||
- "NOW BROADCASTING! [ The Show S06E07 720p WEB-DL DD 5.1 H.264 - LP ]"
|
||||
pattern: ^NOW BROADCASTING! \[(.*)\]
|
||||
vars:
|
||||
- torrentName
|
||||
-
|
||||
test:
|
||||
- "[ Title: S06E07 ] [ Series: Lost ]"
|
||||
- test:
|
||||
- "[ Title: S06E07 ] [ Series: The Show ]"
|
||||
pattern: '^\[ Title: (.*) \] \[ Series: (.*) \]'
|
||||
vars:
|
||||
- title
|
||||
- name1
|
||||
-
|
||||
test:
|
||||
- test:
|
||||
- "[ 2010 ] [ Episode ] [ MKV | x264 | WEB ] [ Uploader: Uploader1 ]"
|
||||
pattern: '^(?:\[ (\d+) \] )?\[ (.*) \] \[ (.*) \] \[ Uploader: (.*?) \](?: \[ Pretime: (.*) \])?'
|
||||
vars:
|
||||
|
@ -57,10 +72,9 @@ parse:
|
|||
- tags
|
||||
- uploader
|
||||
- preTime
|
||||
-
|
||||
test:
|
||||
- test:
|
||||
- "[ https://XXXXXXXXX/torrents.php?id=7338 / https://XXXXXXXXX/torrents.php?action=download&id=9116 ]"
|
||||
pattern: ^\[ .* / (https?://.*id=\d+) \]
|
||||
pattern: ^\[ .* \/ (https?:\/\/.*id=\d+) \]
|
||||
vars:
|
||||
- baseUrl
|
||||
|
||||
|
|
|
@ -14,28 +14,39 @@ supports:
|
|||
source: gazelle
|
||||
settings:
|
||||
- name: authkey
|
||||
type: text
|
||||
type: secret
|
||||
label: Auth key
|
||||
description: Right click DL on a torrent and get the authkey.
|
||||
help: Right click DL on a torrent and get the authkey.
|
||||
- name: torrent_pass
|
||||
type: text
|
||||
type: secret
|
||||
label: Torrent pass
|
||||
description: Right click DL on a torrent and get the torrent_pass.
|
||||
help: Right click DL on a torrent and get the torrent_pass.
|
||||
|
||||
irc:
|
||||
network: DigitalIRC
|
||||
server: irc.empornium.is:6697
|
||||
server: irc.empornium.is
|
||||
port: 6697
|
||||
tls: true
|
||||
channels:
|
||||
- "#empornium-announce"
|
||||
announcers:
|
||||
- "^Wizard^"
|
||||
settings:
|
||||
- name: nickserv.account
|
||||
type: text
|
||||
required: true
|
||||
label: NickServ Account
|
||||
help: NickServ account. Make sure to group your user and bot. Eg. user_bot. Must have staff permission first.
|
||||
- name: nickserv.password
|
||||
type: secret
|
||||
required: true
|
||||
label: NickServ Password
|
||||
help: NickServ password
|
||||
|
||||
parse:
|
||||
type: single
|
||||
lines:
|
||||
-
|
||||
pattern: '^(.*?) - Size: ([0-9]+?.*?) - Uploader: (.*?) - Tags: (.*?) - (https://.*torrents.php\?)id=(.*)$'
|
||||
- pattern: '^(.*?) - Size: ([0-9]+?.*?) - Uploader: (.*?) - Tags: (.*?) - (https://.*torrents.php\?)id=(.*)$'
|
||||
vars:
|
||||
- torrentName
|
||||
- torrentSize
|
||||
|
|
|
@ -14,28 +14,38 @@ supports:
|
|||
source: custom
|
||||
settings:
|
||||
- name: passkey
|
||||
type: text
|
||||
type: secret
|
||||
label: Passkey
|
||||
tooltip: The passkey in your profile.
|
||||
description: "The passkey in your profile."
|
||||
help: "The passkey in your profile."
|
||||
|
||||
irc:
|
||||
network: FileList
|
||||
server: irc.filelist.io:6697
|
||||
server: irc.filelist.io
|
||||
port: 6697
|
||||
tls: true
|
||||
channels:
|
||||
- "#announce"
|
||||
announcers:
|
||||
- Announce
|
||||
settings:
|
||||
- name: nickserv.account
|
||||
type: text
|
||||
required: true
|
||||
label: NickServ Account
|
||||
help: NickServ account. Make sure to group your user and bot. Eg. user_dl
|
||||
- name: nickserv.password
|
||||
type: secret
|
||||
required: false
|
||||
label: NickServ Password
|
||||
help: NickServ password
|
||||
|
||||
parse:
|
||||
type: single
|
||||
lines:
|
||||
-
|
||||
test:
|
||||
- 'New Torrent: This.Really.Old.Movie.1965.DVDRip.DD1.0.x264 -- [Filme SD] [1.91 GB] -- https://filelist.io/details.php?id=746781 -- by uploader1'
|
||||
- 'New Torrent: This.New.Movie.2021.1080p.Blu-ray.AVC.DTS-HD.MA.5.1-BEATRIX -- [FreeLeech!] -- [Filme Blu-Ray] [26.78 GB] -- https://filelist.io/details.php?id=746782 -- by uploader1'
|
||||
- 'New Torrent: This.New.Movie.2021.1080p.Remux.AVC.DTS-HD.MA.5.1-playBD -- [FreeLeech!] -- [Internal!] -- [Filme Blu-Ray] [17.69 GB] -- https://filelist.io/details.php?id=746789 -- by uploader1'
|
||||
- test:
|
||||
- 'New Torrent: This.Really.Old.Movie.1965.DVDRip.DD1.0.x264 -- [Filme SD] [1.91 GB] -- https://filelist.io/details.php?id=000000 -- by uploader1'
|
||||
- 'New Torrent: This.New.Movie.2021.1080p.Blu-ray.AVC.DTS-HD.MA.5.1-BEATRIX -- [FreeLeech!] -- [Filme Blu-Ray] [26.78 GB] -- https://filelist.io/details.php?id=000000 -- by uploader1'
|
||||
- 'New Torrent: This.New.Movie.2021.1080p.Remux.AVC.DTS-HD.MA.5.1-playBD -- [FreeLeech!] -- [Internal!] -- [Filme Blu-Ray] [17.69 GB] -- https://filelist.io/details.php?id=000000 -- by uploader1'
|
||||
pattern: 'New Torrent: (.*?) (?:-- \[(FreeLeech!)] )?(?:-- \[(Internal!)] )?-- \[(.*)] \[(.*)] -- (https?:\/\/filelist.io\/).*id=(.*) -- by (.*)'
|
||||
vars:
|
||||
- torrentName
|
||||
|
|
|
@ -14,32 +14,47 @@ supports:
|
|||
source: gazelle
|
||||
settings:
|
||||
- name: authkey
|
||||
type: text
|
||||
type: secret
|
||||
label: Auth key
|
||||
tooltip: Right click DL on a torrent and get the authkey.
|
||||
description: Right click DL on a torrent and get the authkey.
|
||||
help: Right click DL on a torrent and get the authkey.
|
||||
- name: torrent_pass
|
||||
type: text
|
||||
type: secret
|
||||
label: Torrent pass
|
||||
tooltip: Right click DL on a torrent and get the torrent_pass.
|
||||
description: Right click DL on a torrent and get the torrent_pass.
|
||||
help: Right click DL on a torrent and get the torrent_pass.
|
||||
|
||||
irc:
|
||||
network: GGn
|
||||
server: irc.gazellegames.net:7000
|
||||
server: irc.gazellegames.net
|
||||
port: 7000
|
||||
tls: true
|
||||
channels:
|
||||
- "#GGn-Announce"
|
||||
announcers:
|
||||
- Vertigo
|
||||
settings:
|
||||
- name: nickserv.account
|
||||
type: text
|
||||
required: true
|
||||
label: NickServ Account
|
||||
help: NickServ account. Make sure to group your user and bot. Eg. user|bot
|
||||
- name: nickserv.password
|
||||
type: secret
|
||||
required: true
|
||||
label: NickServ Password
|
||||
help: NickServ password
|
||||
- name: invite_command
|
||||
type: secret
|
||||
default: "Vertigo ENTER #GGn-Announce USERNAME IRCKey"
|
||||
required: true
|
||||
label: Invite command
|
||||
help: Invite auth with Vertigo.
|
||||
|
||||
parse:
|
||||
type: single
|
||||
lines:
|
||||
-
|
||||
test:
|
||||
- "Uploader :-: Nintendo 3DS :-: Yo-Kai.Watch.KOR.3DS-BigBlueBox in Yo-kai Watch [2013] ::Korean, Multi-Region, Scene:: https://gazellegames.net/torrents.php?torrentid=78851 - adventure, role_playing_game, nintendo;"
|
||||
- "Uploader :-: Windows :-: Warriors.Wrath.Evil.Challenge-HI2U in Warriors' Wrath [2016] ::English, Scene:: FREELEECH! :: https://gazellegames.net/torrents.php?torrentid=78902 - action, adventure, casual, indie, role.playing.game;"
|
||||
- test:
|
||||
- "Uploader :-: Nintendo 3DS :-: Cool.Game.KOR.3DS-BigBlueBox in Cool Game [2013] ::Korean, Multi-Region, Scene:: https://gazellegames.net/torrents.php?torrentid=00000 - adventure, role_playing_game, nintendo;"
|
||||
- "Uploader :-: Windows :-: Other.Game-HI2U in Other Game [2016] ::English, Scene:: FREELEECH! :: https://gazellegames.net/torrents.php?torrentid=00000 - action, adventure, casual, indie, role.playing.game;"
|
||||
pattern: '^(.+) :-: (.+) :-: (.+) \[(\d+)\] ::(.+?):: ?(.+? ::)? https?:\/\/([^\/]+\/)torrents.php\?torrentid=(\d+) ?-? ?(.*?)?;?$'
|
||||
vars:
|
||||
- uploader
|
||||
|
|
|
@ -14,26 +14,37 @@ supports:
|
|||
- rss
|
||||
source: xbtit
|
||||
settings:
|
||||
- name: cookie
|
||||
type: text
|
||||
label: Cookie
|
||||
description: "FireFox -> Preferences -> Privacy -> Show Cookies and find the uid and pass cookies. Example: uid=1234; pass=asdf12347asdf13"
|
||||
- name: cookie
|
||||
type: text
|
||||
label: Cookie
|
||||
help: "Check how to get cookies in your browser and find the uid and pass cookies. Example: uid=1234; pass=asdf12347asdf13"
|
||||
|
||||
irc:
|
||||
network: P2P-NET
|
||||
server: irc.p2p-network.net:6697
|
||||
server: irc.p2p-network.net
|
||||
port: 6697
|
||||
tls: true
|
||||
channels:
|
||||
- "#HD-Torrents.Announce"
|
||||
announcers:
|
||||
- HoboLarry
|
||||
settings:
|
||||
- name: nickserv.account
|
||||
type: text
|
||||
required: true
|
||||
label: NickServ Account
|
||||
help: NickServ account. Make sure to group your user and bot. Eg. user|bot
|
||||
- name: nickserv.password
|
||||
type: secret
|
||||
required: true
|
||||
label: NickServ Password
|
||||
help: NickServ password
|
||||
|
||||
parse:
|
||||
type: single
|
||||
lines:
|
||||
-
|
||||
test:
|
||||
- "New Torrent in category [XXX/Blu-ray] Erotische Fantasien 3D (2008) Blu-ray 1080p AVC DTS-HD MA 7 1 (14.60 GB) uploaded! Download: https://hd-torrents.org/download.php?id=806bc36530d146969d300c5352483a5e6e0639e9"
|
||||
- test:
|
||||
- "New Torrent in category [Movies/Remux] That Movie (2008) Blu-ray 1080p REMUX AVC DTS-HD MA 7 1 (14.60 GB) uploaded! Download: https://hd-torrents.org/download.php?id=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
pattern: 'New Torrent in category \[([^\]]*)\] (.*) \(([^\)]*)\) uploaded! Download\: https?\:\/\/([^\/]+\/).*[&\?]id=([a-f0-9]+)'
|
||||
vars:
|
||||
- category
|
||||
|
|
|
@ -15,29 +15,38 @@ supports:
|
|||
source: unknown
|
||||
settings:
|
||||
- name: passkey
|
||||
type: text
|
||||
type: secret
|
||||
label: Passkey
|
||||
tooltip: Copy the passkey from your details page
|
||||
description: "Copy the passkey from your details page."
|
||||
help: "Copy the passkey from your details page."
|
||||
|
||||
irc:
|
||||
network: IPTorrents
|
||||
server: irc.iptorrents.com:6697
|
||||
server: irc.iptorrents.com
|
||||
port: 6697
|
||||
tls: true
|
||||
channels:
|
||||
- "#ipt.announce"
|
||||
- "#ipt.announce2"
|
||||
announcers:
|
||||
- IPT
|
||||
- FunTimes
|
||||
settings:
|
||||
- name: nickserv.account
|
||||
type: text
|
||||
required: true
|
||||
label: NickServ Account
|
||||
help: NickServ account. Make sure to group your user and bot. Eg. user_autodl
|
||||
- name: nickserv.password
|
||||
type: secret
|
||||
required: false
|
||||
label: NickServ Password
|
||||
help: NickServ password
|
||||
|
||||
parse:
|
||||
type: single
|
||||
lines:
|
||||
-
|
||||
test:
|
||||
- "[Movie/XXX] Audrey Bitoni HD Pack FREELEECH - http://www.iptorrents.com/details.php?id=789421 - 14.112 GB"
|
||||
- "[Movies/XviD] The First Men In The Moon 2010 DVDRip XviD-VoMiT - http://www.iptorrents.com/details.php?id=396589 - 716.219 MB"
|
||||
- test:
|
||||
- "[Movies/XviD] The Movie 2010 DVDRip XviD-GROUP FREELEECH - http://www.iptorrents.com/details.php?id=000000 - 716.219 MB"
|
||||
- "[Movies/XviD] The Movie 2010 DVDRip XviD-GROUP - http://www.iptorrents.com/details.php?id=000000 - 716.219 MB"
|
||||
pattern: '^\[([^\]]*)](.*?)\s*(FREELEECH)*\s*-\s+https?\:\/\/([^\/]+).*[&\?]id=(\d+)\s*-(.*)'
|
||||
vars:
|
||||
- category
|
||||
|
|
|
@ -13,33 +13,41 @@ supports:
|
|||
- rss
|
||||
source: gazelle
|
||||
settings:
|
||||
- name: authkey
|
||||
type: text
|
||||
label: Auth key
|
||||
tooltip: Right click DL on a torrent and get the authkey.
|
||||
description: Right click DL on a torrent and get the authkey.
|
||||
- name: torrent_pass
|
||||
type: text
|
||||
label: Torrent pass
|
||||
tooltip: Right click DL on a torrent and get the torrent_pass.
|
||||
description: Right click DL on a torrent and get the torrent_pass.
|
||||
- name: authkey
|
||||
type: secret
|
||||
label: Auth key
|
||||
help: Right click DL on a torrent and get the authkey.
|
||||
- name: torrent_pass
|
||||
type: secret
|
||||
label: Torrent pass
|
||||
help: Right click DL on a torrent and get the torrent_pass.
|
||||
|
||||
irc:
|
||||
network: Nebulance
|
||||
server: irc.nebulance.cc:6697
|
||||
server: irc.nebulance.cc
|
||||
port: 6697
|
||||
channels:
|
||||
- "#nbl-announce"
|
||||
announcers:
|
||||
- DRADIS
|
||||
settings:
|
||||
- name: nickserv.account
|
||||
type: text
|
||||
required: true
|
||||
label: NickServ Account
|
||||
help: NickServ account. Make sure to group your user and bot. Eg. user|bot
|
||||
- name: nickserv.password
|
||||
type: secret
|
||||
required: true
|
||||
label: NickServ Password
|
||||
help: NickServ password
|
||||
|
||||
parse:
|
||||
type: single
|
||||
lines:
|
||||
-
|
||||
test:
|
||||
- "[Episodes] The Vet Life - S02E08 [WebRip / x264 / MKV / 720p / HD / VLAD / The.Vet.Life.S02E08.Tuskegee.Reunion.720p.ANPL.WEBRip.AAC2.0.x264-VLAD.mkv] [702.00 MB - Uploader: UPLOADER] - http://nebulance.io/torrents.php?id=147 [Tags: comedy,subtitles,cbs]"
|
||||
- "[Seasons] Police Interceptors - S10 [HDTV / x264 / MKV / MP4 / 480p / SD / BTN / Police.Interceptors.S10.HDTV.x264-BTN] [5.27 GB - Uploader: UPLOADER] - http://nebulance.io/torrents.php?id=1472 [Tags: comedy,subtitles,cbs]"
|
||||
- test:
|
||||
- "[Episodes] The Show - S02E08 [WebRip / x264 / MKV / 720p / HD / VLAD / The.Show.S02E08.Episode.Name.720p.ANPL.WEBRip.AAC2.0.x264-GROUP.mkv] [702.00 MB - Uploader: UPLOADER] - http://nebulance.io/torrents.php?id=000 [Tags: comedy,subtitles,cbs]"
|
||||
- "[Seasons] Other Show - S10 [HDTV / x264 / MKV / MP4 / 480p / SD / BTN / Other.Show.S10.HDTV.x264-GROUP] [5.27 GB - Uploader: UPLOADER] - http://nebulance.io/torrents.php?id=0000 [Tags: comedy,subtitles,cbs]"
|
||||
pattern: '\[(.*?)\] (.*?) \[(.*?)\] \[(.*?) - Uploader: (.*?)\] - (https?://.*)id=(\d+) \[Tags: (.*)\]'
|
||||
vars:
|
||||
- category
|
||||
|
|
|
@ -13,33 +13,48 @@ supports:
|
|||
- rss
|
||||
source: gazelle
|
||||
settings:
|
||||
- name: authkey
|
||||
type: text
|
||||
label: Auth key
|
||||
tooltip: Right click DL on a torrent and get the authkey.
|
||||
description: Right click DL on a torrent and get the authkey.
|
||||
- name: torrent_pass
|
||||
type: text
|
||||
label: Torrent pass
|
||||
tooltip: Right click DL on a torrent and get the torrent_pass.
|
||||
description: Right click DL on a torrent and get the torrent_pass.
|
||||
- name: authkey
|
||||
type: text
|
||||
label: Auth key
|
||||
help: Right click DL on a torrent and get the authkey.
|
||||
- name: torrent_pass
|
||||
type: text
|
||||
label: Torrent pass
|
||||
help: Right click DL on a torrent and get the torrent_pass.
|
||||
|
||||
irc:
|
||||
network: Orpheus
|
||||
server: irc.orpheus.network:7000
|
||||
server: irc.orpheus.network
|
||||
port: 7000
|
||||
tls: true
|
||||
channels:
|
||||
- "#announce"
|
||||
announcers:
|
||||
- hermes
|
||||
settings:
|
||||
- name: nickserv.account
|
||||
type: text
|
||||
required: true
|
||||
label: NickServ Account
|
||||
help: NickServ account. Make sure to group your user and bot. Eg. user|bot
|
||||
- name: nickserv.password
|
||||
type: secret
|
||||
required: true
|
||||
label: NickServ Password
|
||||
help: NickServ password
|
||||
- name: invite_command
|
||||
type: secret
|
||||
default: "hermes enter #announce USERNAME IRCKey"
|
||||
required: true
|
||||
label: Invite command
|
||||
help: Invite auth with Hermes.
|
||||
|
||||
parse:
|
||||
type: single
|
||||
lines:
|
||||
-
|
||||
test:
|
||||
- "TORRENT: Todd Edwards - You Came To Me [2002] [Single] - FLAC / Lossless / WEB - 2000s,house,uk.garage,garage.house - https://orpheus.network/torrents.php?id=756102 / https://orpheus.network/torrents.php?action=download&id=1647868"
|
||||
- "TORRENT: THE BOOK [2021] [Album] - FLAC / Lossless / CD - - https://orpheus.network/torrents.php?id=693523 / https://orpheus.network/torrents.php?action=download&id=1647867"
|
||||
- test:
|
||||
- "TORRENT: That Artist - Albuum [2002] [Single] - FLAC / Lossless / WEB - 2000s,house,uk.garage,garage.house - https://orpheus.network/torrents.php?id=000000 / https://orpheus.network/torrents.php?action=download&id=0000000"
|
||||
- "TORRENT: Something [2021] [Album] - FLAC / Lossless / CD - - https://orpheus.network/torrents.php?id=000000 / https://orpheus.network/torrents.php?action=download&id=0000000"
|
||||
pattern: 'TORRENT: (.*) - (.*) - https?://.* / (https?://.*id=\d+)'
|
||||
vars:
|
||||
- torrentName
|
||||
|
|
|
@ -13,33 +13,48 @@ supports:
|
|||
- rss
|
||||
source: gazelle
|
||||
settings:
|
||||
- name: authkey
|
||||
type: text
|
||||
label: Auth key
|
||||
tooltip: Right click DL on a torrent and get the authkey.
|
||||
description: Right click DL on a torrent and get the authkey.
|
||||
- name: torrent_pass
|
||||
type: text
|
||||
label: Torrent pass
|
||||
tooltip: Right click DL on a torrent and get the torrent_pass.
|
||||
description: Right click DL on a torrent and get the torrent_pass.
|
||||
- name: authkey
|
||||
type: secret
|
||||
label: Auth key
|
||||
help: Right click DL on a torrent and get the authkey.
|
||||
- name: torrent_pass
|
||||
type: secret
|
||||
label: Torrent pass
|
||||
help: Right click DL on a torrent and get the torrent_pass.
|
||||
|
||||
irc:
|
||||
network: PassThePopcorn
|
||||
server: irc.passthepopcorn.me:7000
|
||||
server: irc.passthepopcorn.me
|
||||
port: 7000
|
||||
tls: true
|
||||
channels:
|
||||
- "#ptp-announce"
|
||||
announcers:
|
||||
- Hummingbird
|
||||
settings:
|
||||
- name: nickserv.account
|
||||
type: text
|
||||
required: true
|
||||
label: NickServ Account
|
||||
help: NickServ account. Make sure to group your user and bot. Eg. user|autodl
|
||||
- name: nickserv.password
|
||||
type: secret
|
||||
required: true
|
||||
label: NickServ Password
|
||||
help: NickServ password
|
||||
- name: invite_command
|
||||
type: secret
|
||||
default: "Hummingbird ENTER USERNAME IRCKey #ptp-announce"
|
||||
required: true
|
||||
label: Invite command
|
||||
help: Invite auth with Hummingbird.
|
||||
|
||||
parse:
|
||||
type: single
|
||||
lines:
|
||||
-
|
||||
test:
|
||||
- "Irene Huss - Nattrond AKA The Night Round [2008] by Anders Engström - XviD / DVD / AVI / 640x352 - http://passthepopcorn.me/torrents.php?id=51627 / http://passthepopcorn.me/torrents.php?action=download&id=97333 - crime, drama, mystery"
|
||||
- "Dirty Rotten Scoundrels [1988] by Frank Oz - x264 / Blu-ray / MKV / 720p - http://passthepopcorn.me/torrents.php?id=10735 / http://passthepopcorn.me/torrents.php?action=download&id=97367 - comedy, crime"
|
||||
- test:
|
||||
- "That Movie [2008] by Director - XviD / DVD / AVI / 640x352 - http://passthepopcorn.me/torrents.php?id=00000 / http://passthepopcorn.me/torrents.php?action=download&id=00000 - crime, drama, mystery"
|
||||
- "Some Old Movie [1988] by Director - x264 / Blu-ray / MKV / 720p - http://passthepopcorn.me/torrents.php?id=00000 / http://passthepopcorn.me/torrents.php?action=download&id=00000 - comedy, crime"
|
||||
pattern: '^(.*)-\s*https?:.*[&\?]id=.*https?\:\/\/([^\/]+\/).*[&\?]id=(\d+)\s*-\s*(.*)'
|
||||
vars:
|
||||
- torrentName
|
||||
|
|
|
@ -13,33 +13,48 @@ supports:
|
|||
- rss
|
||||
source: gazelle
|
||||
settings:
|
||||
- name: authkey
|
||||
type: text
|
||||
label: Auth key
|
||||
tooltip: Right click DL on a torrent and get the authkey.
|
||||
description: Right click DL on a torrent and get the authkey.
|
||||
- name: torrent_pass
|
||||
type: text
|
||||
label: Torrent pass
|
||||
tooltip: Right click DL on a torrent and get the torrent_pass.
|
||||
description: Right click DL on a torrent and get the torrent_pass.
|
||||
- name: authkey
|
||||
type: secret
|
||||
label: Auth key
|
||||
help: Right click DL on a torrent and get the authkey.
|
||||
- name: torrent_pass
|
||||
type: secret
|
||||
label: Torrent pass
|
||||
help: Right click DL on a torrent and get the torrent_pass.
|
||||
|
||||
irc:
|
||||
network: Scratch-Network
|
||||
server: irc.scratch-network.net:6697
|
||||
server: irc.scratch-network.net
|
||||
port: 6697
|
||||
tls: true
|
||||
channels:
|
||||
- "#red-announce"
|
||||
announcers:
|
||||
- Drone
|
||||
settings:
|
||||
- name: nickserv.account
|
||||
type: text
|
||||
required: true
|
||||
label: NickServ Account
|
||||
help: NickServ account. Make sure to group your user and bot. Eg. user-autodl
|
||||
- name: nickserv.password
|
||||
type: secret
|
||||
required: true
|
||||
label: NickServ Password
|
||||
help: NickServ password
|
||||
- name: invite_command
|
||||
type: secret
|
||||
default: "Drone enter #red-announce USERNAME IRCKey"
|
||||
required: true
|
||||
label: Invite command
|
||||
help: Invite auth with Drone.
|
||||
|
||||
parse:
|
||||
type: single
|
||||
lines:
|
||||
-
|
||||
test:
|
||||
- "JR Get Money - Nobody But You [2008] [Single] - FLAC / Lossless / Log / 100% / Cue / CD - https://redacted.ch/torrents.php?id=1592366 / https://redacted.ch/torrents.php?action=download&id=3372962 - hip.hop,rhythm.and.blues,2000s"
|
||||
- "Johann Sebastian Bach performed by Festival Strings Lucerne under Rudolf Baumgartner - Brandenburg Concertos 5 and 6, Suite No 2 [1991] [Album] - FLAC / Lossless / Log / 100% / Cue / CD - https://redacted.ch/torrents.php?id=1592367 / https://redacted.ch/torrents.php?action=download&id=3372963 - classical"
|
||||
- test:
|
||||
- "Artist - Albumname [2008] [Single] - FLAC / Lossless / Log / 100% / Cue / CD - https://redacted.ch/torrents.php?id=0000000 / https://redacted.ch/torrents.php?action=download&id=0000000 - hip.hop,rhythm.and.blues,2000s"
|
||||
- "A really long name here - Concertos 5 and 6, Suite No 2 [1991] [Album] - FLAC / Lossless / Log / 100% / Cue / CD - https://redacted.ch/torrents.php?id=0000000 / https://redacted.ch/torrents.php?action=download&id=0000000 - classical"
|
||||
pattern: '^(.*)\s+-\s+https?:.*[&\?]id=.*https?\:\/\/([^\/]+\/).*[&\?]id=(\d+)\s*-\s*(.*)'
|
||||
vars:
|
||||
- torrentName
|
||||
|
|
|
@ -14,27 +14,37 @@ supports:
|
|||
source: rartracker
|
||||
settings:
|
||||
- name: passkey
|
||||
type: text
|
||||
type: secret
|
||||
label: Passkey
|
||||
tooltip: Copy the passkey from the /rss page
|
||||
description: "Copy the passkey from the /rss page."
|
||||
help: "Copy the passkey from the /rss page."
|
||||
|
||||
irc:
|
||||
network: SuperBits
|
||||
server: irc.superbits.org:6697
|
||||
server: irc.superbits.org
|
||||
port: 6697
|
||||
tls: true
|
||||
channels:
|
||||
- "#autodl"
|
||||
announcers:
|
||||
- SuperBits
|
||||
settings:
|
||||
- name: nickserv.account
|
||||
type: text
|
||||
required: true
|
||||
label: NickServ Account
|
||||
help: NickServ account. Make sure to group your user and bot. Eg. user-bot
|
||||
- name: nickserv.password
|
||||
type: secret
|
||||
required: false
|
||||
label: NickServ Password
|
||||
help: NickServ password
|
||||
|
||||
parse:
|
||||
type: single
|
||||
lines:
|
||||
-
|
||||
test:
|
||||
- "-[archive Film 1080]2[Asterix.Et.La.Surprise.De.Cesar.1985.FRENCH.1080p.BluRay.x264-TSuNaMi]3[844551]4[Size: 4.41 GB]5[FL: no]6[Scene: yes]"
|
||||
- "-[new TV]2[Party.Down.South.S05E05.720p.WEB.h264-DiRT]3[844557]4[Size: 964.04 MB]5[FL: no]6[Scene: yes]7[Pred 1m 30s ago]"
|
||||
- test:
|
||||
- "-[archive Film 1080]2[A.Movie.1985.FRENCH.1080p.BluRay.x264-GROUP]3[000000]4[Size: 4.41 GB]5[FL: no]6[Scene: yes]"
|
||||
- "-[new TV]2[Some.Show.S05E05.720p.WEB.h264-GROUP]3[000000]4[Size: 964.04 MB]5[FL: no]6[Scene: yes]7[Pred 1m 30s ago]"
|
||||
pattern: '\-\[(.*)\]2\[(.*)\]3\[(\d+)\]4\[Size\:\s(.*)\]5\[FL\:\s(no|yes)\]6\[Scene\:\s(no|yes)\](?:7\[Pred\s(.*)\sago\])?'
|
||||
vars:
|
||||
- category
|
||||
|
|
|
@ -14,28 +14,38 @@ supports:
|
|||
source: custom
|
||||
settings:
|
||||
- name: rsskey
|
||||
type: text
|
||||
type: secret
|
||||
label: RSS key
|
||||
tooltip: The rsskey in your TorrentLeech RSS feed link.
|
||||
description: "Go to your profile and copy and paste your RSS link to extract the rsskey."
|
||||
help: "Go to your profile and copy your RSS key"
|
||||
regex: /([\da-fA-F]{20})
|
||||
|
||||
irc:
|
||||
network: TorrentLeech.org
|
||||
server: irc.torrentleech.org:7021
|
||||
server: irc.torrentleech.org
|
||||
port: 7021
|
||||
tls: true
|
||||
channels:
|
||||
- "#tlannounces"
|
||||
announcers:
|
||||
- _AnnounceBot_
|
||||
settings:
|
||||
- name: nickserv.account
|
||||
type: text
|
||||
required: false
|
||||
label: NickServ Account
|
||||
help: NickServ account. Make sure to group your user and bot. Eg. user_bot
|
||||
- name: nickserv.password
|
||||
type: secret
|
||||
required: false
|
||||
label: NickServ Password
|
||||
help: NickServ password
|
||||
|
||||
parse:
|
||||
type: single
|
||||
lines:
|
||||
-
|
||||
test:
|
||||
- "New Torrent Announcement: <PC :: Iso> Name:'debian live 10 6 0 amd64 standard iso' uploaded by 'Anonymous' - http://www.tracker01.test/torrent/263302"
|
||||
- "New Torrent Announcement: <PC :: Iso> Name:'debian live 10 6 0 amd64 standard iso' uploaded by 'Anonymous' freeleech - http://www.tracker01.test/torrent/263302"
|
||||
- test:
|
||||
- "New Torrent Announcement: <PC :: Iso> Name:'debian live 10 6 0 amd64 standard iso' uploaded by 'Anonymous' - http://www.tracker01.test/torrent/000000"
|
||||
- "New Torrent Announcement: <PC :: Iso> Name:'debian live 10 6 0 amd64 standard iso' uploaded by 'Anonymous' freeleech - http://www.tracker01.test/torrent/000000"
|
||||
pattern: New Torrent Announcement:\s*<([^>]*)>\s*Name:'(.*)' uploaded by '([^']*)'\s*(freeleech)*\s*-\s*https?\:\/\/([^\/]+\/)torrent\/(\d+)
|
||||
vars:
|
||||
- category
|
||||
|
|
|
@ -13,33 +13,48 @@ supports:
|
|||
- rss
|
||||
source: gazelle
|
||||
settings:
|
||||
- name: authkey
|
||||
type: text
|
||||
label: Auth key
|
||||
tooltip: Right click DL on a torrent and get the authkey.
|
||||
description: Right click DL on a torrent and get the authkey.
|
||||
- name: torrent_pass
|
||||
type: text
|
||||
label: Torrent pass
|
||||
tooltip: Right click DL on a torrent and get the torrent_pass.
|
||||
description: Right click DL on a torrent and get the torrent_pass.
|
||||
- name: authkey
|
||||
type: secret
|
||||
label: Auth key
|
||||
help: Right click DL on a torrent and get the authkey.
|
||||
- name: torrent_pass
|
||||
type: secret
|
||||
label: Torrent pass
|
||||
help: Right click DL on a torrent and get the torrent_pass.
|
||||
|
||||
irc:
|
||||
network: P2P-Network
|
||||
server: irc.p2p-network.net:6697
|
||||
server: irc.p2p-network.net
|
||||
port: 6697
|
||||
tls: true
|
||||
channels:
|
||||
- "#UHD.Announce"
|
||||
announcers:
|
||||
- UHDBot
|
||||
- cr0nusbot
|
||||
settings:
|
||||
- name: nickserv.account
|
||||
type: text
|
||||
required: true
|
||||
label: NickServ Account
|
||||
help: NickServ account. Make sure to group your user and bot. Eg. user|autodl
|
||||
- name: nickserv.password
|
||||
type: secret
|
||||
required: true
|
||||
label: NickServ Password
|
||||
help: NickServ password
|
||||
- name: invite_command
|
||||
type: secret
|
||||
default: "UHDBot invite IRCKey"
|
||||
required: true
|
||||
label: Invite command
|
||||
help: Invite auth with UHDBot.
|
||||
|
||||
parse:
|
||||
type: single
|
||||
lines:
|
||||
-
|
||||
test:
|
||||
- "New Torrent: D'Ardennen [2015] - TayTO Type: Movie / 1080p / Encode / Freeleech: 100 Size: 7.00GB - https://uhdbits.org/torrents.php?id=13882 / https://uhdbits.org/torrents.php?action=download&id=20488"
|
||||
- test:
|
||||
- "New Torrent: A Movie [2015] - GROUP Type: Movie / 1080p / Encode / Freeleech: 100 Size: 7.00GB - https://uhdbits.org/torrents.php?id=00000 / https://uhdbits.org/torrents.php?action=download&id=00000"
|
||||
pattern: 'New Torrent: (.*) Type: (.*?) Freeleech: (.*) Size: (.*) - https?:\/\/.* \/ (https?:\/\/.*id=\d+)'
|
||||
vars:
|
||||
- torrentName
|
||||
|
|
|
@ -5,9 +5,8 @@ import (
|
|||
"io/fs"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
)
|
||||
|
@ -26,9 +25,15 @@ type Service interface {
|
|||
}
|
||||
|
||||
type service struct {
|
||||
repo domain.IndexerRepo
|
||||
indexerDefinitions map[string]domain.IndexerDefinition
|
||||
indexerInstances map[string]domain.IndexerDefinition
|
||||
repo domain.IndexerRepo
|
||||
|
||||
// contains all raw indexer definitions
|
||||
indexerDefinitions map[string]domain.IndexerDefinition
|
||||
|
||||
// contains indexers with data set
|
||||
indexerInstances map[string]domain.IndexerDefinition
|
||||
|
||||
// map server:channel:announce to indexer.Identifier
|
||||
mapIndexerIRCToName map[string]string
|
||||
}
|
||||
|
||||
|
@ -44,6 +49,14 @@ func NewService(repo domain.IndexerRepo) Service {
|
|||
func (s *service) Store(indexer domain.Indexer) (*domain.Indexer, error) {
|
||||
i, err := s.repo.Store(indexer)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msgf("failed to store indexer: %v", indexer.Name)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// add to indexerInstances
|
||||
err = s.addIndexer(*i)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msgf("failed to add indexer: %v", indexer.Name)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -56,6 +69,13 @@ func (s *service) Update(indexer domain.Indexer) (*domain.Indexer, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// add to indexerInstances
|
||||
err = s.addIndexer(*i)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msgf("failed to add indexer: %v", indexer.Name)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
|
@ -94,46 +114,60 @@ func (s *service) GetAll() ([]*domain.IndexerDefinition, error) {
|
|||
var res = make([]*domain.IndexerDefinition, 0)
|
||||
|
||||
for _, indexer := range indexers {
|
||||
in := s.getDefinitionByName(indexer.Identifier)
|
||||
if in == nil {
|
||||
// if no indexerDefinition found, continue
|
||||
indexerDefinition, err := s.mapIndexer(indexer)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
temp := domain.IndexerDefinition{
|
||||
ID: indexer.ID,
|
||||
Name: in.Name,
|
||||
Identifier: in.Identifier,
|
||||
Enabled: indexer.Enabled,
|
||||
Description: in.Description,
|
||||
Language: in.Language,
|
||||
Privacy: in.Privacy,
|
||||
Protocol: in.Protocol,
|
||||
URLS: in.URLS,
|
||||
Settings: nil,
|
||||
SettingsMap: make(map[string]string),
|
||||
IRC: in.IRC,
|
||||
Parse: in.Parse,
|
||||
if indexerDefinition == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// map settings
|
||||
// add value to settings objects
|
||||
for _, setting := range in.Settings {
|
||||
if v, ok := indexer.Settings[setting.Name]; ok {
|
||||
setting.Value = v
|
||||
|
||||
temp.SettingsMap[setting.Name] = v
|
||||
}
|
||||
|
||||
temp.Settings = append(temp.Settings, setting)
|
||||
}
|
||||
|
||||
res = append(res, &temp)
|
||||
res = append(res, indexerDefinition)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) mapIndexer(indexer domain.Indexer) (*domain.IndexerDefinition, error) {
|
||||
|
||||
in := s.getDefinitionByName(indexer.Identifier)
|
||||
if in == nil {
|
||||
// if no indexerDefinition found, continue
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
indexerDefinition := domain.IndexerDefinition{
|
||||
ID: int(indexer.ID),
|
||||
Name: in.Name,
|
||||
Identifier: in.Identifier,
|
||||
Enabled: indexer.Enabled,
|
||||
Description: in.Description,
|
||||
Language: in.Language,
|
||||
Privacy: in.Privacy,
|
||||
Protocol: in.Protocol,
|
||||
URLS: in.URLS,
|
||||
Settings: nil,
|
||||
SettingsMap: make(map[string]string),
|
||||
IRC: in.IRC,
|
||||
Parse: in.Parse,
|
||||
}
|
||||
|
||||
// map settings
|
||||
// add value to settings objects
|
||||
for _, setting := range in.Settings {
|
||||
if v, ok := indexer.Settings[setting.Name]; ok {
|
||||
setting.Value = v
|
||||
|
||||
indexerDefinition.SettingsMap[setting.Name] = v
|
||||
}
|
||||
|
||||
indexerDefinition.Settings = append(indexerDefinition.Settings, setting)
|
||||
}
|
||||
|
||||
return &indexerDefinition, nil
|
||||
}
|
||||
|
||||
func (s *service) GetTemplates() ([]domain.IndexerDefinition, error) {
|
||||
|
||||
definitions := s.indexerDefinitions
|
||||
|
@ -152,44 +186,81 @@ func (s *service) Start() error {
|
|||
return err
|
||||
}
|
||||
|
||||
indexers, err := s.GetAll()
|
||||
indexerDefinitions, err := s.GetAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, indexer := range indexers {
|
||||
if !indexer.Enabled {
|
||||
continue
|
||||
}
|
||||
for _, indexerDefinition := range indexerDefinitions {
|
||||
s.indexerInstances[indexerDefinition.Identifier] = *indexerDefinition
|
||||
|
||||
s.indexerInstances[indexer.Identifier] = *indexer
|
||||
|
||||
// map irc stuff to indexer.name
|
||||
if indexer.IRC != nil {
|
||||
server := indexer.IRC.Server
|
||||
|
||||
for _, channel := range indexer.IRC.Channels {
|
||||
for _, announcer := range indexer.IRC.Announcers {
|
||||
val := fmt.Sprintf("%v:%v:%v", server, channel, announcer)
|
||||
s.mapIndexerIRCToName[val] = indexer.Identifier
|
||||
}
|
||||
}
|
||||
}
|
||||
s.mapIRCIndexerLookup(indexerDefinition.Identifier, *indexerDefinition)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) removeIndexer(indexer domain.Indexer) error {
|
||||
|
||||
delete(s.indexerDefinitions, indexer.Identifier)
|
||||
|
||||
// TODO delete from mapIndexerIRCToName
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) addIndexer(indexer domain.Indexer) error {
|
||||
|
||||
// TODO only add if not already there?? Overwrite?
|
||||
|
||||
indexerDefinition, err := s.mapIndexer(indexer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO only add enabled?
|
||||
//if !indexer.Enabled {
|
||||
// continue
|
||||
//}
|
||||
|
||||
s.indexerInstances[indexerDefinition.Identifier] = *indexerDefinition
|
||||
|
||||
s.mapIRCIndexerLookup(indexer.Identifier, *indexerDefinition)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) mapIRCIndexerLookup(indexerIdentifier string, indexerDefinition domain.IndexerDefinition) {
|
||||
// map irc stuff to indexer.name
|
||||
// map[irc.network.test:channel:announcer1] = indexer1
|
||||
// map[irc.network.test:channel:announcer2] = indexer2
|
||||
if indexerDefinition.IRC != nil {
|
||||
server := indexerDefinition.IRC.Server
|
||||
channels := indexerDefinition.IRC.Channels
|
||||
announcers := indexerDefinition.IRC.Announcers
|
||||
|
||||
for _, channel := range channels {
|
||||
for _, announcer := range announcers {
|
||||
// format to server:channel:announcer
|
||||
val := fmt.Sprintf("%v:%v:%v", server, channel, announcer)
|
||||
val = strings.ToLower(val)
|
||||
|
||||
s.mapIndexerIRCToName[val] = indexerIdentifier
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LoadIndexerDefinitions load definitions from golang embed fs
|
||||
func (s *service) LoadIndexerDefinitions() error {
|
||||
|
||||
entries, err := fs.ReadDir(Definitions, "definitions")
|
||||
if err != nil {
|
||||
log.Fatal().Msgf("failed reading directory: %s", err)
|
||||
log.Fatal().Stack().Msgf("failed reading directory: %s", err)
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
log.Fatal().Msgf("failed reading directory: %s", err)
|
||||
log.Fatal().Stack().Msgf("failed reading directory: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -197,19 +268,19 @@ func (s *service) LoadIndexerDefinitions() error {
|
|||
filePath := "definitions/" + f.Name()
|
||||
|
||||
if strings.Contains(f.Name(), ".yaml") {
|
||||
log.Debug().Msgf("parsing: %v", filePath)
|
||||
log.Trace().Msgf("parsing: %v", filePath)
|
||||
|
||||
var d domain.IndexerDefinition
|
||||
|
||||
data, err := fs.ReadFile(Definitions, filePath)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msgf("failed reading file: %v", filePath)
|
||||
log.Error().Stack().Err(err).Msgf("failed reading file: %v", filePath)
|
||||
return err
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(data, &d)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("failed unmarshal file: %v", filePath)
|
||||
log.Error().Stack().Err(err).Msgf("failed unmarshal file: %v", filePath)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -217,10 +288,13 @@ func (s *service) LoadIndexerDefinitions() error {
|
|||
}
|
||||
}
|
||||
|
||||
log.Info().Msgf("Loaded %d indexer definitions", len(s.indexerDefinitions))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) GetIndexerByAnnounce(name string) *domain.IndexerDefinition {
|
||||
name = strings.ToLower(name)
|
||||
|
||||
if identifier, idOk := s.mapIndexerIRCToName[name]; idOk {
|
||||
if indexer, ok := s.indexerInstances[identifier]; ok {
|
||||
|
|
|
@ -25,14 +25,19 @@ type Handler struct {
|
|||
network *domain.IrcNetwork
|
||||
announceService announce.Service
|
||||
|
||||
client *irc.Client
|
||||
conn net.Conn
|
||||
ctx context.Context
|
||||
stopped chan struct{}
|
||||
cancel context.CancelFunc
|
||||
|
||||
lastPing time.Time
|
||||
lastAnnounce time.Time
|
||||
}
|
||||
|
||||
func NewHandler(network domain.IrcNetwork, announceService announce.Service) *Handler {
|
||||
return &Handler{
|
||||
client: nil,
|
||||
conn: nil,
|
||||
ctx: nil,
|
||||
stopped: make(chan struct{}),
|
||||
|
@ -44,7 +49,7 @@ func NewHandler(network domain.IrcNetwork, announceService announce.Service) *Ha
|
|||
func (s *Handler) Run() error {
|
||||
//log.Debug().Msgf("server %+v", s.network)
|
||||
|
||||
if s.network.Addr == "" {
|
||||
if s.network.Server == "" {
|
||||
return errors.New("addr not set")
|
||||
}
|
||||
|
||||
|
@ -59,7 +64,7 @@ func (s *Handler) Run() error {
|
|||
var netConn net.Conn
|
||||
var err error
|
||||
|
||||
addr := s.network.Addr
|
||||
addr := fmt.Sprintf("%v:%v", s.network.Server, s.network.Port)
|
||||
|
||||
// decide to use SSL or not
|
||||
if s.network.TLS {
|
||||
|
@ -88,9 +93,9 @@ func (s *Handler) Run() error {
|
|||
log.Info().Msgf("Connected to: %v", addr)
|
||||
|
||||
config := irc.ClientConfig{
|
||||
Nick: s.network.Nick,
|
||||
User: s.network.Nick,
|
||||
Name: s.network.Nick,
|
||||
Nick: s.network.NickServ.Account,
|
||||
User: s.network.NickServ.Account,
|
||||
Name: s.network.NickServ.Account,
|
||||
Pass: s.network.Pass,
|
||||
Handler: irc.HandlerFunc(func(c *irc.Client, m *irc.Message) {
|
||||
switch m.Command {
|
||||
|
@ -101,38 +106,66 @@ func (s *Handler) Run() error {
|
|||
log.Error().Msgf("error joining channels %v", err)
|
||||
}
|
||||
|
||||
case "366":
|
||||
// TODO: handle joined
|
||||
log.Debug().Msgf("JOINED: %v", m)
|
||||
// 322 TOPIC
|
||||
// 333 UP
|
||||
// 353 @
|
||||
// 396 Displayed host
|
||||
case "366": // JOINED
|
||||
s.handleJoined(m)
|
||||
|
||||
case "JOIN":
|
||||
log.Debug().Msgf("%v: JOIN %v", s.network.Server, m.Trailing())
|
||||
|
||||
case "433":
|
||||
// TODO: handle nick in use
|
||||
log.Debug().Msgf("NICK IN USE: %v", m)
|
||||
log.Debug().Msgf("%v: NICK IN USE: %v", s.network.Server, m)
|
||||
|
||||
case "448", "475", "477":
|
||||
case "448", "473", "475", "477":
|
||||
// TODO: handle join failed
|
||||
log.Debug().Msgf("JOIN FAILED: %v", m)
|
||||
log.Debug().Msgf("%v: JOIN FAILED %v: %v", s.network.Server, m.Params[1], m.Trailing())
|
||||
|
||||
case "900": // Invite bot logged in
|
||||
log.Debug().Msgf("%v: %v", s.network.Server, m.Trailing())
|
||||
|
||||
case "KICK":
|
||||
log.Debug().Msgf("KICK: %v", m)
|
||||
log.Debug().Msgf("%v: KICK: %v", s.network.Server, m)
|
||||
|
||||
case "MODE":
|
||||
// TODO: handle mode change
|
||||
log.Debug().Msgf("MODE CHANGE: %v", m)
|
||||
err := s.handleMode(m)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error MODE change: %v", m)
|
||||
}
|
||||
|
||||
case "INVITE":
|
||||
// TODO: handle invite
|
||||
log.Debug().Msgf("INVITE: %v", m)
|
||||
log.Debug().Msgf("%v: INVITE: %v", s.network.Server, m)
|
||||
|
||||
case "PART":
|
||||
// TODO: handle parted
|
||||
log.Debug().Msgf("PART: %v", m)
|
||||
log.Debug().Msgf("%v: PART: %v", s.network.Server, m)
|
||||
|
||||
case "PRIVMSG":
|
||||
err := s.onMessage(m)
|
||||
if err != nil {
|
||||
log.Error().Msgf("error on message %v", err)
|
||||
}
|
||||
|
||||
case "CAP":
|
||||
log.Debug().Msgf("%v: CAP: %v", s.network.Server, m)
|
||||
|
||||
case "NOTICE":
|
||||
log.Trace().Msgf("%v: %v", s.network.Server, m.Trailing())
|
||||
|
||||
case "PING":
|
||||
err := s.handlePing(m)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err)
|
||||
}
|
||||
|
||||
//case "372":
|
||||
// log.Debug().Msgf("372: %v", m)
|
||||
default:
|
||||
log.Trace().Msgf("%v: %v", s.network.Server, m.Trailing())
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
@ -140,6 +173,8 @@ func (s *Handler) Run() error {
|
|||
// Create the client
|
||||
client := irc.NewClient(s.conn, config)
|
||||
|
||||
s.client = client
|
||||
|
||||
// Connect
|
||||
err = client.RunContext(ctx)
|
||||
if err != nil {
|
||||
|
@ -157,9 +192,9 @@ func (s *Handler) GetNetwork() *domain.IrcNetwork {
|
|||
func (s *Handler) Stop() {
|
||||
s.cancel()
|
||||
|
||||
//if !s.isStopped() {
|
||||
// close(s.stopped)
|
||||
//}
|
||||
if !s.isStopped() {
|
||||
close(s.stopped)
|
||||
}
|
||||
|
||||
if s.conn != nil {
|
||||
s.conn.Close()
|
||||
|
@ -176,41 +211,40 @@ func (s *Handler) isStopped() bool {
|
|||
}
|
||||
|
||||
func (s *Handler) onConnect(client *irc.Client, channels []domain.IrcChannel) error {
|
||||
// TODO check commands like nickserv before joining
|
||||
identified := false
|
||||
|
||||
for _, command := range s.network.ConnectCommands {
|
||||
cmd := strings.TrimLeft(command, "/")
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
log.Info().Msgf("send connect command: %v to network: %s", cmd, s.network.Name)
|
||||
|
||||
err := client.Write(cmd)
|
||||
if s.network.NickServ.Password != "" {
|
||||
err := s.handleNickServPRIVMSG(s.network.NickServ.Account, s.network.NickServ.Password)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error sending connect command %v to network: %v", command, s.network.Name)
|
||||
continue
|
||||
//return err
|
||||
log.Error().Err(err).Msgf("error nickserv: %v", s.network.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
identified = true
|
||||
}
|
||||
|
||||
for _, ch := range channels {
|
||||
myChan := fmt.Sprintf("JOIN %s", ch.Name)
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// handle channel password
|
||||
if ch.Password != "" {
|
||||
myChan = fmt.Sprintf("JOIN %s %s", ch.Name, ch.Password)
|
||||
}
|
||||
if s.network.InviteCommand != "" {
|
||||
|
||||
err := client.Write(myChan)
|
||||
err := s.handleInvitePRIVMSG(s.network.InviteCommand)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error joining channel: %v", ch.Name)
|
||||
continue
|
||||
//return err
|
||||
log.Error().Err(err).Msgf("error sending connect command %v to network: %v", s.network.InviteCommand, s.network.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Msgf("Monitoring channel %s", ch.Name)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
if !identified {
|
||||
for _, channel := range channels {
|
||||
err := s.handleJoinChannel(channel.Name)
|
||||
if err != nil {
|
||||
log.Error().Err(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -221,7 +255,12 @@ func (s *Handler) OnJoin(msg string) (interface{}, error) {
|
|||
}
|
||||
|
||||
func (s *Handler) onMessage(msg *irc.Message) error {
|
||||
log.Debug().Msgf("msg: %v", msg)
|
||||
//log.Debug().Msgf("raw msg: %v", msg)
|
||||
|
||||
// check if message is from announce bot and correct channel, if not return
|
||||
//if msg.Name != s.network. {
|
||||
//
|
||||
//}
|
||||
|
||||
// parse announce
|
||||
channel := &msg.Params[0]
|
||||
|
@ -231,10 +270,14 @@ func (s *Handler) onMessage(msg *irc.Message) error {
|
|||
|
||||
// add correlationID and tracing
|
||||
|
||||
announceID := fmt.Sprintf("%v:%v:%v", s.network.Addr, *channel, *announcer)
|
||||
announceID := fmt.Sprintf("%v:%v:%v", s.network.Server, *channel, *announcer)
|
||||
announceID = strings.ToLower(announceID)
|
||||
|
||||
// clean message
|
||||
cleanedMsg := cleanMessage(message)
|
||||
log.Debug().Msgf("%v: %v %v: %v", s.network.Server, *channel, *announcer, cleanedMsg)
|
||||
|
||||
s.lastAnnounce = time.Now()
|
||||
|
||||
go func() {
|
||||
err := s.announceService.Parse(announceID, cleanedMsg)
|
||||
|
@ -246,6 +289,145 @@ func (s *Handler) onMessage(msg *irc.Message) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Handler) sendPrivMessage(msg string) error {
|
||||
msg = strings.TrimLeft(msg, "/")
|
||||
privMsg := fmt.Sprintf("PRIVMSG %s", msg)
|
||||
|
||||
err := s.client.Write(privMsg)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("could not send priv msg: %v", msg)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Handler) handleJoinChannel(channel string) error {
|
||||
m := irc.Message{
|
||||
Command: "JOIN",
|
||||
Params: []string{channel},
|
||||
}
|
||||
|
||||
log.Debug().Msgf("%v: %v", s.network.Server, m.String())
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
err := s.client.Write(m.String())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error handling join: %v", m.String())
|
||||
return err
|
||||
}
|
||||
|
||||
//log.Info().Msgf("Monitoring channel %v %s", s.network.Name, channel)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Handler) handleJoined(msg *irc.Message) {
|
||||
log.Debug().Msgf("%v: JOINED: %v", s.network.Server, msg.Trailing())
|
||||
|
||||
log.Info().Msgf("%v: Monitoring channel %s", s.network.Server, msg.Params[1])
|
||||
}
|
||||
|
||||
func (s *Handler) handleInvitePRIVMSG(msg string) error {
|
||||
msg = strings.TrimPrefix(msg, "/msg")
|
||||
split := strings.Split(msg, " ")
|
||||
|
||||
m := irc.Message{
|
||||
Command: "PRIVMSG",
|
||||
Params: split,
|
||||
}
|
||||
|
||||
log.Info().Msgf("%v: Invite command: %v", s.network.Server, m.String())
|
||||
|
||||
err := s.client.Write(m.String())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error handling invite: %v", m.String())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Handler) handlePRIVMSG(msg string) error {
|
||||
msg = strings.TrimLeft(msg, "/")
|
||||
|
||||
m := irc.Message{
|
||||
Command: "PRIVMSG",
|
||||
Params: []string{msg},
|
||||
}
|
||||
log.Debug().Msgf("%v: Handle privmsg: %v", s.network.Server, m.String())
|
||||
|
||||
err := s.client.Write(m.String())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error handling PRIVMSG: %v", m.String())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Handler) handleNickServPRIVMSG(nick, password string) error {
|
||||
m := irc.Message{
|
||||
Command: "PRIVMSG",
|
||||
Params: []string{"NickServ", "IDENTIFY", nick, password},
|
||||
}
|
||||
|
||||
log.Debug().Msgf("%v: NickServ: %v", s.network.Server, m.String())
|
||||
|
||||
err := s.client.Write(m.String())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error identifying with nickserv: %v", m.String())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Handler) handleMode(msg *irc.Message) error {
|
||||
log.Debug().Msgf("%v: MODE: %v %v", s.network.Server, msg.User, msg.Trailing())
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
if s.network.NickServ.Password != "" && !strings.Contains(msg.String(), s.client.CurrentNick()) || !strings.Contains(msg.String(), "+r") {
|
||||
log.Trace().Msgf("%v: MODE: Not correct permission yet: %v", s.network.Server, msg.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, ch := range s.network.Channels {
|
||||
err := s.handleJoinChannel(ch.Name)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error joining channel: %v", ch.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Handler) handlePing(msg *irc.Message) error {
|
||||
//log.Trace().Msgf("%v: %v", s.network.Server, msg)
|
||||
|
||||
pong := irc.Message{
|
||||
Command: "PONG",
|
||||
Params: msg.Params,
|
||||
}
|
||||
|
||||
log.Trace().Msgf("%v: %v", s.network.Server, pong.String())
|
||||
|
||||
err := s.client.Write(pong.String())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error PING PONG response: %v", pong.String())
|
||||
return err
|
||||
}
|
||||
|
||||
s.lastPing = time.Now()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// irc line can contain lots of extra stuff like color so lets clean that
|
||||
func cleanMessage(message string) string {
|
||||
var regexMessageClean = `\x0f|\x1f|\x02|\x03(?:[\d]{1,2}(?:,[\d]{1,2})?)?`
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
type Service interface {
|
||||
StartHandlers()
|
||||
StopHandlers()
|
||||
StopNetwork(name string) error
|
||||
ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error)
|
||||
GetNetworkByID(id int64) (*domain.IrcNetwork, error)
|
||||
|
@ -56,7 +57,7 @@ func (s *service) StartHandlers() {
|
|||
s.lock.Lock()
|
||||
channels, err := s.repo.ListChannels(network.ID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("failed to list channels for network %q", network.Addr)
|
||||
log.Error().Err(err).Msgf("failed to list channels for network %q", network.Server)
|
||||
}
|
||||
network.Channels = channels
|
||||
|
||||
|
@ -79,6 +80,15 @@ func (s *service) StartHandlers() {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *service) StopHandlers() {
|
||||
for _, handler := range s.handlers {
|
||||
log.Info().Msgf("stopping network: %+v", handler.network.Name)
|
||||
handler.Stop()
|
||||
}
|
||||
|
||||
log.Info().Msg("stopped all irc handlers")
|
||||
}
|
||||
|
||||
func (s *service) startNetwork(network domain.IrcNetwork) error {
|
||||
// look if we have the network in handlers already, if so start it
|
||||
if handler, found := s.handlers[network.Name]; found {
|
||||
|
@ -134,7 +144,7 @@ func (s *service) GetNetworkByID(id int64) (*domain.IrcNetwork, error) {
|
|||
|
||||
channels, err := s.repo.ListChannels(network.ID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("failed to list channels for network %q", network.Addr)
|
||||
log.Error().Err(err).Msgf("failed to list channels for network %q", network.Server)
|
||||
return nil, err
|
||||
}
|
||||
network.Channels = append(network.Channels, channels...)
|
||||
|
@ -154,7 +164,7 @@ func (s *service) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error)
|
|||
for _, n := range networks {
|
||||
channels, err := s.repo.ListChannels(n.ID)
|
||||
if err != nil {
|
||||
log.Error().Msgf("failed to list channels for network %q: %v", n.Addr, err)
|
||||
log.Error().Msgf("failed to list channels for network %q: %v", n.Server, err)
|
||||
return nil, err
|
||||
}
|
||||
n.Channels = append(n.Channels, channels...)
|
||||
|
@ -166,11 +176,21 @@ func (s *service) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error)
|
|||
}
|
||||
|
||||
func (s *service) DeleteNetwork(ctx context.Context, id int64) error {
|
||||
if err := s.repo.DeleteNetwork(ctx, id); err != nil {
|
||||
network, err := s.GetNetworkByID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug().Msgf("delete network: %+v", id)
|
||||
log.Debug().Msgf("delete network: %v", id)
|
||||
|
||||
// Remove network and handler
|
||||
if err = s.StopNetwork(network.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = s.repo.DeleteNetwork(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -191,24 +211,40 @@ func (s *service) StoreNetwork(network *domain.IrcNetwork) error {
|
|||
}
|
||||
|
||||
// stop or start network
|
||||
if !network.Enabled {
|
||||
log.Debug().Msgf("stopping network: %+v", network.Name)
|
||||
|
||||
err := s.StopNetwork(network.Name)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("could not stop network: %+v", network.Name)
|
||||
return fmt.Errorf("could not stop network: %v", network.Name)
|
||||
}
|
||||
} else {
|
||||
log.Debug().Msgf("starting network: %+v", network.Name)
|
||||
|
||||
if network.Enabled {
|
||||
err := s.startNetwork(*network)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("could not start network: %+v", network.Name)
|
||||
return fmt.Errorf("could not start network: %v", network.Name)
|
||||
}
|
||||
|
||||
} else {
|
||||
err := s.StopNetwork(network.Name)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("could not stop network: %+v", network.Name)
|
||||
return fmt.Errorf("could not stop network: %v", network.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// stop or start network
|
||||
//if !network.Enabled {
|
||||
// log.Debug().Msgf("stopping network: %+v", network.Name)
|
||||
//
|
||||
// err := s.StopNetwork(network.Name)
|
||||
// if err != nil {
|
||||
// log.Error().Err(err).Msgf("could not stop network: %+v", network.Name)
|
||||
// return fmt.Errorf("could not stop network: %v", network.Name)
|
||||
// }
|
||||
//} else {
|
||||
// log.Debug().Msgf("starting network: %+v", network.Name)
|
||||
//
|
||||
// err := s.startNetwork(*network)
|
||||
// if err != nil {
|
||||
// log.Error().Err(err).Msgf("could not start network: %+v", network.Name)
|
||||
// return fmt.Errorf("could not start network: %v", network.Name)
|
||||
// }
|
||||
//}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@ func Setup(cfg domain.Config) {
|
|||
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
||||
case "WARN":
|
||||
zerolog.SetGlobalLevel(zerolog.WarnLevel)
|
||||
case "TRACE":
|
||||
zerolog.SetGlobalLevel(zerolog.TraceLevel)
|
||||
default:
|
||||
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ func NewService(actionService action.Service) Service {
|
|||
}
|
||||
|
||||
func (s *service) Process(announce domain.Announce) error {
|
||||
log.Debug().Msgf("start to process release: %+v", announce)
|
||||
log.Trace().Msgf("start to process release: %+v", announce)
|
||||
|
||||
if announce.Filter.Actions == nil {
|
||||
return fmt.Errorf("no actions for filter: %v", announce.Filter.Name)
|
||||
|
|
|
@ -41,3 +41,10 @@ func (s *Server) Start() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Shutdown() {
|
||||
log.Info().Msg("Shutting down server")
|
||||
|
||||
// stop all irc handlers
|
||||
s.ircService.StopHandlers()
|
||||
}
|
||||
|
|
|
@ -7,10 +7,11 @@ interface Props {
|
|||
name: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
defaultValue?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const SwitchGroup: React.FC<Props> = ({name, label, description}) => (
|
||||
const SwitchGroup: React.FC<Props> = ({name, label, description, defaultValue}) => (
|
||||
<ul className="mt-2 divide-y divide-gray-200">
|
||||
<Switch.Group as="li" className="py-4 flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
|
@ -27,6 +28,7 @@ const SwitchGroup: React.FC<Props> = ({name, label, description}) => (
|
|||
|
||||
<Field
|
||||
name={name}
|
||||
defaultValue={defaultValue as any}
|
||||
render={({input: {onChange, checked, value}}) => (
|
||||
<Switch
|
||||
value={value}
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
import {Field} from "react-final-form";
|
||||
import { Field } from "react-final-form";
|
||||
import React from "react";
|
||||
import Error from "./Error";
|
||||
import {classNames} from "../../styles/utils";
|
||||
import { classNames } from "../../styles/utils";
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
label?: string;
|
||||
help?: string;
|
||||
placeholder?: string;
|
||||
defaultValue?: string;
|
||||
className?: string;
|
||||
required?: boolean;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
const TextFieldWide: React.FC<Props> = ({name, label, placeholder, required, className}) => (
|
||||
<div
|
||||
const TextFieldWide: React.FC<Props> = ({ name, label, help, placeholder, defaultValue, required, hidden, className}) => (
|
||||
<div hidden={hidden}
|
||||
className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
<div>
|
||||
|
||||
|
@ -23,17 +26,22 @@ const TextFieldWide: React.FC<Props> = ({name, label, placeholder, required, cla
|
|||
<div className="sm:col-span-2">
|
||||
<Field
|
||||
name={name}
|
||||
render={({input, meta}) => (
|
||||
defaultValue={defaultValue}
|
||||
render={({ input, meta }) => (
|
||||
<input
|
||||
{...input}
|
||||
id={name}
|
||||
type="text"
|
||||
className={classNames(meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 focus:border-indigo-500 border-gray-300", "block w-full shadow-sm sm:text-sm rounded-md")}
|
||||
placeholder={placeholder}
|
||||
hidden={hidden}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Error name={name} classNames="block text-red-500 mt-2"/>
|
||||
{help && (
|
||||
<p className="mt-2 text-sm text-gray-500" id="email-description">{help}</p>
|
||||
)}
|
||||
<Error name={name} classNames="block text-red-500 mt-2" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -7,15 +7,19 @@ interface Props {
|
|||
name: string;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
defaultValue?: number;
|
||||
className?: string;
|
||||
required?: boolean;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
const NumberFieldWide: React.FC<Props> = ({
|
||||
name,
|
||||
label,
|
||||
placeholder,
|
||||
defaultValue,
|
||||
required,
|
||||
hidden,
|
||||
className,
|
||||
}) => (
|
||||
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
|
@ -30,6 +34,7 @@ const NumberFieldWide: React.FC<Props> = ({
|
|||
<div className="sm:col-span-2">
|
||||
<Field
|
||||
name={name}
|
||||
defaultValue={defaultValue}
|
||||
parse={(v) => v & parseInt(v, 10)}
|
||||
render={({ input, meta }) => (
|
||||
<input
|
||||
|
|
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 PasswordFieldWide } from "./PasswordField";
|
||||
export { default as RadioFieldsetWide } from "./RadioFieldsetWide";
|
||||
export { default as SelectFieldWide } from "./SelectField";
|
||||
|
|
|
@ -31,6 +31,43 @@ export interface Indexer {
|
|||
settings: object | any;
|
||||
}
|
||||
|
||||
export interface IndexerSchema {
|
||||
// id: number;
|
||||
name: string;
|
||||
identifier: string;
|
||||
description: string;
|
||||
language: string;
|
||||
privacy: string;
|
||||
protocol: string;
|
||||
urls: string[];
|
||||
settings: IndexerSchemaSettings[];
|
||||
irc: IndexerSchemaIRC;
|
||||
}
|
||||
|
||||
|
||||
export interface IndexerSchemaSettings {
|
||||
name: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
label: string;
|
||||
help: string;
|
||||
description: string;
|
||||
default: string;
|
||||
}
|
||||
|
||||
export interface IndexerSchemaIRC {
|
||||
network: string;
|
||||
server: string;
|
||||
port: number;
|
||||
tls: boolean;
|
||||
nickserv: boolean;
|
||||
announcers: string[];
|
||||
channels: string[];
|
||||
invite: string[];
|
||||
invite_command: string;
|
||||
settings: IndexerSchemaSettings[];
|
||||
}
|
||||
|
||||
export interface Filter {
|
||||
id: number;
|
||||
name: string;
|
||||
|
@ -87,16 +124,29 @@ export interface DownloadClient {
|
|||
settings: object;
|
||||
}
|
||||
|
||||
export interface NickServ {
|
||||
account: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface Network {
|
||||
id: number;
|
||||
id?: number;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
addr: string;
|
||||
nick: string;
|
||||
username: string;
|
||||
realname: string;
|
||||
pass: string;
|
||||
sasl: SASL;
|
||||
server: string;
|
||||
port: number;
|
||||
tls: boolean;
|
||||
invite_command: string;
|
||||
nickserv: {
|
||||
account: string;
|
||||
password: string;
|
||||
}
|
||||
channels: Channel[];
|
||||
settings: object;
|
||||
}
|
||||
|
||||
export interface Channel {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface SASL {
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
import React, {Fragment} from "react";
|
||||
import {useMutation, useQuery} from "react-query";
|
||||
import {Indexer} from "../../domain/interfaces";
|
||||
import {sleep} from "../../utils/utils";
|
||||
import {XIcon} from "@heroicons/react/solid";
|
||||
import {Dialog, Transition} from "@headlessui/react";
|
||||
import {Field, Form} from "react-final-form";
|
||||
import React, { Fragment } from "react";
|
||||
import { useMutation, useQuery } from "react-query";
|
||||
import { Channel, Indexer, IndexerSchema, IndexerSchemaSettings, Network } from "../../domain/interfaces";
|
||||
import { sleep } from "../../utils/utils";
|
||||
import { XIcon } from "@heroicons/react/solid";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Field, Form } from "react-final-form";
|
||||
import DEBUG from "../../components/debug";
|
||||
import Select from "react-select";
|
||||
import {queryClient} from "../../App";
|
||||
import { SwitchGroup } from "../../components/inputs";
|
||||
import { queryClient } from "../../App";
|
||||
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
|
||||
import APIClient from "../../api/APIClient";
|
||||
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
|
||||
|
||||
interface props {
|
||||
isOpen: boolean;
|
||||
toggle: any;
|
||||
}
|
||||
|
||||
function IndexerAddForm({isOpen, toggle}: props) {
|
||||
const {data} = useQuery<any[], Error>('indexerSchema', APIClient.indexers.getSchema,
|
||||
function IndexerAddForm({ isOpen, toggle }: props) {
|
||||
const { data } = useQuery<IndexerSchema[], Error>('indexerSchema', APIClient.indexers.getSchema,
|
||||
{
|
||||
enabled: isOpen,
|
||||
refetchOnWindowFocus: false
|
||||
|
@ -33,59 +34,122 @@ function IndexerAddForm({isOpen, toggle}: props) {
|
|||
}
|
||||
})
|
||||
|
||||
const onSubmit = (data: any) => {
|
||||
mutation.mutate(data)
|
||||
const ircMutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
|
||||
onSuccess: (data) => {
|
||||
console.log("irc mutation: ", data);
|
||||
|
||||
// queryClient.invalidateQueries(['indexer']);
|
||||
// sleep(1500)
|
||||
|
||||
// toggle()
|
||||
}
|
||||
})
|
||||
|
||||
const onSubmit = (formData: any) => {
|
||||
let ind = data && data.find(i => i.identifier === formData.identifier)
|
||||
|
||||
if (!ind) {
|
||||
return
|
||||
}
|
||||
|
||||
let channels: Channel[] = []
|
||||
if (ind.irc.channels.length) {
|
||||
ind.irc.channels.forEach(element => {
|
||||
channels.push({ name: element })
|
||||
});
|
||||
}
|
||||
|
||||
const network: Network = {
|
||||
name: ind.name,
|
||||
enabled: false,
|
||||
server: formData.irc.server,
|
||||
port: formData.irc.port,
|
||||
tls: formData.irc.tls,
|
||||
nickserv: formData.irc.nickserv,
|
||||
invite_command: formData.irc.invite_command,
|
||||
settings: formData.irc.settings,
|
||||
channels: channels,
|
||||
}
|
||||
|
||||
console.log("network: ", network);
|
||||
|
||||
|
||||
mutation.mutate(formData, {
|
||||
onSuccess: (data) => {
|
||||
// create irc
|
||||
ircMutation.mutate(network)
|
||||
}
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
const renderSettingFields = (indexer: string) => {
|
||||
if (indexer !== "") {
|
||||
// let ind = data.find(i => i.implementation_name === indexer)
|
||||
let ind = data && data.find(i => i.identifier === indexer)
|
||||
|
||||
return (
|
||||
<div key="opt">
|
||||
{ind && ind.settings && ind.settings.map((f: any, idx: number) => {
|
||||
switch (f.type) {
|
||||
case "text":
|
||||
return (
|
||||
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5" key={idx}>
|
||||
<div>
|
||||
<label
|
||||
htmlFor={f.name}
|
||||
className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2"
|
||||
>
|
||||
{f.label}
|
||||
</label>
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
<Field name={"settings."+f.name}>
|
||||
{({input, meta}) => (
|
||||
<div className="sm:col-span-2">
|
||||
<input
|
||||
type="text"
|
||||
{...input}
|
||||
className="block w-full shadow-sm sm:text-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 rounded-md"
|
||||
/>
|
||||
{meta.touched && meta.error &&
|
||||
<span>{meta.error}</span>}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})}
|
||||
{ind && ind.settings && ind.settings.map((f: any, idx: number) => {
|
||||
switch (f.type) {
|
||||
case "text":
|
||||
return (
|
||||
<TextFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} defaultValue=""/>
|
||||
)
|
||||
case "secret":
|
||||
return (
|
||||
<PasswordFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} defaultValue="" />
|
||||
)
|
||||
}
|
||||
})}
|
||||
<div hidden={true}>
|
||||
<TextFieldWide name={`name`} label="Name" defaultValue={ind?.name} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const renderIrcSettingFields = (indexer: string) => {
|
||||
|
||||
if (indexer !== "") {
|
||||
let ind = data && data.find(i => i.identifier === indexer)
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{ind && ind.irc && ind.irc.settings && (
|
||||
<div className="border-t border-gray-200 py-5">
|
||||
<div className="px-6 space-y-1">
|
||||
<Dialog.Title className="text-lg font-medium text-gray-900">IRC</Dialog.Title>
|
||||
<p className="text-sm text-gray-500">
|
||||
Networks, channels and invite commands are configured automatically.
|
||||
</p>
|
||||
</div>
|
||||
{ind.irc.settings.map((f: IndexerSchemaSettings, idx: number) => {
|
||||
switch (f.type) {
|
||||
case "text":
|
||||
return <TextFieldWide name={`irc.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} />
|
||||
case "secret":
|
||||
return <PasswordFieldWide name={`irc.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} defaultValue={f.default} />
|
||||
}
|
||||
})}
|
||||
|
||||
<div hidden={true}>
|
||||
<TextFieldWide name={`irc.server`} label="Server" defaultValue={ind.irc.server} />
|
||||
<NumberFieldWide name={`irc.port`} label="Port" defaultValue={ind.irc.port} />
|
||||
<SwitchGroup name="irc.tls" label="TLS" defaultValue={ind.irc.tls} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition.Root show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" static className="fixed inset-0 overflow-hidden" open={isOpen} onClose={toggle}>
|
||||
<div className="absolute inset-0 overflow-hidden">
|
||||
<Dialog.Overlay className="absolute inset-0"/>
|
||||
<Dialog.Overlay className="absolute inset-0" />
|
||||
|
||||
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
|
||||
<Transition.Child
|
||||
|
@ -100,18 +164,17 @@ function IndexerAddForm({isOpen, toggle}: props) {
|
|||
<div className="w-screen max-w-2xl">
|
||||
<Form
|
||||
initialValues={{
|
||||
name: "",
|
||||
enabled: true,
|
||||
identifier: "",
|
||||
irc: {}
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({handleSubmit, values}) => {
|
||||
{({ handleSubmit, values }) => {
|
||||
return (
|
||||
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
|
||||
onSubmit={handleSubmit}>
|
||||
onSubmit={handleSubmit}>
|
||||
<div className="flex-1">
|
||||
{/* Header */}
|
||||
<div className="px-4 py-6 bg-gray-50 sm:px-6">
|
||||
<div className="flex items-start justify-between space-x-3">
|
||||
<div className="space-y-1">
|
||||
|
@ -129,43 +192,14 @@ function IndexerAddForm({isOpen, toggle}: props) {
|
|||
onClick={toggle}
|
||||
>
|
||||
<span className="sr-only">Close panel</span>
|
||||
<XIcon className="h-6 w-6" aria-hidden="true"/>
|
||||
<XIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider container */}
|
||||
<div
|
||||
className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<div
|
||||
className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
</div>
|
||||
<Field name="name">
|
||||
{({input, meta}) => (
|
||||
<div className="sm:col-span-2">
|
||||
<input
|
||||
type="text"
|
||||
{...input}
|
||||
className="block w-full shadow-sm sm:text-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 rounded-md"
|
||||
/>
|
||||
{meta.touched && meta.error &&
|
||||
<span>{meta.error}</span>}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<SwitchGroup name="enabled" label="Enabled" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
|
@ -182,26 +216,32 @@ function IndexerAddForm({isOpen, toggle}: props) {
|
|||
name="identifier"
|
||||
parse={val => val && val.value}
|
||||
format={val => data && data.find((o: any) => o.value === val)}
|
||||
render={({input, meta}) => (
|
||||
render={({ input, meta }) => (
|
||||
<React.Fragment>
|
||||
<Select {...input}
|
||||
isClearable={true}
|
||||
placeholder="Choose an indexer"
|
||||
options={data && data.sort((a,b): any => a.name.localeCompare(b.name)).map(v => ({
|
||||
label: v.name,
|
||||
value: v.identifier
|
||||
// value: v.implementation_name
|
||||
}))}/>
|
||||
{/*<Error name={input.name} classNames="text-red mt-2 block" />*/}
|
||||
isClearable={true}
|
||||
placeholder="Choose an indexer"
|
||||
|
||||
options={data && data.sort((a, b): any => a.name.localeCompare(b.name)).map(v => ({
|
||||
label: v.name,
|
||||
value: v.identifier
|
||||
}))} />
|
||||
</React.Fragment>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<SwitchGroup name="enabled" label="Enabled" />
|
||||
</div>
|
||||
|
||||
|
||||
{renderSettingFields(values.identifier)}
|
||||
|
||||
</div>
|
||||
|
||||
{renderIrcSettingFields(values.identifier)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
@ -223,7 +263,7 @@ function IndexerAddForm({isOpen, toggle}: props) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<DEBUG values={values}/>
|
||||
<DEBUG values={values} />
|
||||
</form>
|
||||
)
|
||||
}}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import {Fragment, useRef} from "react";
|
||||
import {useMutation } from "react-query";
|
||||
import {Indexer} from "../../domain/interfaces";
|
||||
import {sleep} from "../../utils/utils";
|
||||
import {ExclamationIcon, XIcon} from "@heroicons/react/solid";
|
||||
import {Dialog, Transition} from "@headlessui/react";
|
||||
import {Field, Form} from "react-final-form";
|
||||
import { Fragment, useRef } from "react";
|
||||
import { useMutation } from "react-query";
|
||||
import { Indexer } from "../../domain/interfaces";
|
||||
import { sleep } from "../../utils/utils";
|
||||
import { ExclamationIcon, XIcon } from "@heroicons/react/solid";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Field, Form } from "react-final-form";
|
||||
import DEBUG from "../../components/debug";
|
||||
import { SwitchGroup } from "../../components/inputs";
|
||||
import {useToggle} from "../../hooks/hooks";
|
||||
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
|
||||
import { useToggle } from "../../hooks/hooks";
|
||||
import APIClient from "../../api/APIClient";
|
||||
import {queryClient} from "../../App";
|
||||
import { queryClient } from "../../App";
|
||||
import { PasswordFieldWide } from "../../components/inputs/wide";
|
||||
|
||||
interface props {
|
||||
isOpen: boolean;
|
||||
|
@ -17,7 +18,7 @@ interface props {
|
|||
indexer: Indexer;
|
||||
}
|
||||
|
||||
function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
||||
function IndexerUpdateForm({ isOpen, toggle, indexer }: props) {
|
||||
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false)
|
||||
|
||||
const mutation = useMutation((indexer: Indexer) => APIClient.indexers.update(indexer), {
|
||||
|
@ -55,31 +56,11 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
|||
switch (f.type) {
|
||||
case "text":
|
||||
return (
|
||||
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5" key={idx}>
|
||||
<div>
|
||||
<label
|
||||
htmlFor={f.name}
|
||||
className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2"
|
||||
>
|
||||
{f.label}
|
||||
</label>
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
<Field name={"settings."+f.name}>
|
||||
{({input, meta}) => (
|
||||
<div className="sm:col-span-2">
|
||||
<input
|
||||
type="text"
|
||||
{...input}
|
||||
className="block w-full shadow-sm sm:text-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 rounded-md"
|
||||
/>
|
||||
{meta.touched && meta.error &&
|
||||
<span>{meta.error}</span>}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
<TextFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} />
|
||||
)
|
||||
case "secret":
|
||||
return (
|
||||
<PasswordFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} />
|
||||
)
|
||||
}
|
||||
})}
|
||||
|
@ -88,9 +69,6 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
|||
}
|
||||
}
|
||||
|
||||
// const setss = indexer.settings.reduce((o: any, obj: any) => ({ ...o, [obj.name]: obj.value }), {})
|
||||
// console.log("setts", setss)
|
||||
|
||||
return (
|
||||
<Transition.Root show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" static className="fixed inset-0 overflow-hidden" open={isOpen} onClose={toggle}>
|
||||
|
@ -119,8 +97,8 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
|||
|
||||
{/* This element is to trick the browser into centering the modal contents. */}
|
||||
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
|
||||
​
|
||||
</span>
|
||||
​
|
||||
</span>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
|
@ -172,7 +150,7 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
|||
</Dialog>
|
||||
</Transition.Root>
|
||||
<div className="absolute inset-0 overflow-hidden">
|
||||
<Dialog.Overlay className="absolute inset-0"/>
|
||||
<Dialog.Overlay className="absolute inset-0" />
|
||||
|
||||
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
|
||||
<Transition.Child
|
||||
|
@ -195,12 +173,11 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
|||
}}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({handleSubmit, values}) => {
|
||||
{({ handleSubmit, values }) => {
|
||||
return (
|
||||
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
|
||||
onSubmit={handleSubmit}>
|
||||
onSubmit={handleSubmit}>
|
||||
<div className="flex-1">
|
||||
{/* Header */}
|
||||
<div className="px-4 py-6 bg-gray-50 sm:px-6">
|
||||
<div className="flex items-start justify-between space-x-3">
|
||||
<div className="space-y-1">
|
||||
|
@ -218,13 +195,12 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
|||
onClick={toggle}
|
||||
>
|
||||
<span className="sr-only">Close panel</span>
|
||||
<XIcon className="h-6 w-6" aria-hidden="true"/>
|
||||
<XIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider container */}
|
||||
<div
|
||||
className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<div
|
||||
|
@ -238,7 +214,7 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
|||
</label>
|
||||
</div>
|
||||
<Field name="name">
|
||||
{({input, meta}) => (
|
||||
{({ input, meta }) => (
|
||||
<div className="sm:col-span-2">
|
||||
<input
|
||||
type="text"
|
||||
|
@ -246,7 +222,7 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
|||
className="block w-full shadow-sm sm:text-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 rounded-md"
|
||||
/>
|
||||
{meta.touched && meta.error &&
|
||||
<span>{meta.error}</span>}
|
||||
<span>{meta.error}</span>}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
|
@ -289,7 +265,7 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<DEBUG values={values}/>
|
||||
<DEBUG values={values} />
|
||||
</form>
|
||||
)
|
||||
}}
|
||||
|
|
|
@ -5,26 +5,14 @@ import {Dialog, Transition} from "@headlessui/react";
|
|||
import {XIcon} from "@heroicons/react/solid";
|
||||
import {Field, Form} from "react-final-form";
|
||||
import DEBUG from "../../components/debug";
|
||||
import {SwitchGroup, TextAreaWide, TextFieldWide} from "../../components/inputs";
|
||||
import {SwitchGroup, TextFieldWide} from "../../components/inputs";
|
||||
import {queryClient} from "../../App";
|
||||
|
||||
import arrayMutators from "final-form-arrays";
|
||||
import { FieldArray } from "react-final-form-arrays";
|
||||
import {classNames} from "../../styles/utils";
|
||||
import APIClient from "../../api/APIClient";
|
||||
|
||||
|
||||
// interface radioFieldsetOption {
|
||||
// label: string;
|
||||
// description: string;
|
||||
// value: string;
|
||||
// }
|
||||
|
||||
// const saslTypeOptions: radioFieldsetOption[] = [
|
||||
// {label: "None", description: "None", value: ""},
|
||||
// {label: "Plain", description: "SASL plain", value: "PLAIN"},
|
||||
// {label: "NickServ", description: "/NS identify", value: "NICKSERV"},
|
||||
// ];
|
||||
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
|
||||
|
||||
function IrcNetworkAddForm({isOpen, toggle}: any) {
|
||||
const mutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
|
||||
|
@ -53,12 +41,8 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
|
|||
errors.name = "Required";
|
||||
}
|
||||
|
||||
if (!values.addr) {
|
||||
errors.addr = "Required";
|
||||
}
|
||||
|
||||
if (!values.nick) {
|
||||
errors.nick = "Required";
|
||||
if (!values.server) {
|
||||
errors.server = "Required";
|
||||
}
|
||||
|
||||
return errors;
|
||||
|
@ -86,18 +70,9 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
|
|||
initialValues={{
|
||||
name: "",
|
||||
enabled: true,
|
||||
addr: "",
|
||||
server: "",
|
||||
tls: false,
|
||||
nick: "",
|
||||
pass: "",
|
||||
// connect_commands: "",
|
||||
// sasl: {
|
||||
// mechanism: "",
|
||||
// plain: {
|
||||
// username: "",
|
||||
// password: "",
|
||||
// }
|
||||
// },
|
||||
}}
|
||||
mutators={{
|
||||
...arrayMutators
|
||||
|
@ -110,7 +85,6 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
|
|||
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
|
||||
onSubmit={handleSubmit}>
|
||||
<div className="flex-1">
|
||||
{/* Header */}
|
||||
<div className="px-4 py-6 bg-gray-50 sm:px-6">
|
||||
<div className="flex items-start justify-between space-x-3">
|
||||
<div className="space-y-1">
|
||||
|
@ -144,88 +118,19 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
<TextFieldWide name="addr" label="Address" placeholder="Address:port eg irc.server.net:6697" required={true} />
|
||||
<TextFieldWide name="server" label="Server" placeholder="Address: Eg irc.server.net" required={true} />
|
||||
<NumberFieldWide name="port" label="Port" required={true} />
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<SwitchGroup name="tls" label="TLS"/>
|
||||
</div>
|
||||
|
||||
<TextFieldWide name="nick" label="Nick" placeholder="Nick" required={true} />
|
||||
<PasswordFieldWide name="pass" label="Password" help="Network password" />
|
||||
|
||||
<TextFieldWide name="password" label="Password" placeholder="Network password" />
|
||||
<TextFieldWide name="nickserv.account" label="NickServ Account" required={true} />
|
||||
<PasswordFieldWide name="nickserv.password" label="NickServ Password" />
|
||||
|
||||
<TextAreaWide name="connect_commands" label="Connect commands" placeholder="/msg test this" />
|
||||
|
||||
|
||||
{/* <Field*/}
|
||||
{/* name="sasl.mechanism"*/}
|
||||
{/* type="select"*/}
|
||||
{/* render={({input}) => (*/}
|
||||
{/* <Listbox value={input.value} onChange={input.onChange}>*/}
|
||||
{/* {({open}) => (*/}
|
||||
{/* <div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">*/}
|
||||
{/* <div>*/}
|
||||
{/* <Listbox.Label className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2">SASL / auth</Listbox.Label>*/}
|
||||
{/* </div>*/}
|
||||
{/* <div className="sm:col-span-2 relative">*/}
|
||||
{/* <Listbox.Button*/}
|
||||
{/* className="bg-white relative w-full border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">*/}
|
||||
{/* <span className="block truncate">{input.value ? saslTypeOptions.find(c => c.value === input.value)!.label : "Choose auth method"}</span>*/}
|
||||
{/* <span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">*/}
|
||||
{/* <SelectorIcon className="h-5 w-5 text-gray-400" aria-hidden="true"/>*/}
|
||||
{/*</span>*/}
|
||||
{/* </Listbox.Button>*/}
|
||||
|
||||
{/* <Transition*/}
|
||||
{/* show={open}*/}
|
||||
{/* as={Fragment}*/}
|
||||
{/* leave="transition ease-in duration-100"*/}
|
||||
{/* leaveFrom="opacity-100"*/}
|
||||
{/* leaveTo="opacity-0"*/}
|
||||
{/* >*/}
|
||||
{/* <Listbox.Options*/}
|
||||
{/* static*/}
|
||||
{/* className="absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"*/}
|
||||
{/* >*/}
|
||||
{/* {saslTypeOptions.map((opt: any) => (*/}
|
||||
{/* <Listbox.Option*/}
|
||||
{/* key={opt.value}*/}
|
||||
{/* className={({active}) =>*/}
|
||||
{/* classNames(*/}
|
||||
{/* active ? 'text-white bg-indigo-600' : 'text-gray-900',*/}
|
||||
{/* 'cursor-default select-none relative py-2 pl-3 pr-9'*/}
|
||||
{/* )*/}
|
||||
{/* }*/}
|
||||
{/* value={opt.value}*/}
|
||||
{/* >*/}
|
||||
{/* {({selected, active}) => (*/}
|
||||
{/* <>*/}
|
||||
{/* <span className={classNames(selected ? 'font-semibold' : 'font-normal', 'block truncate')}>*/}
|
||||
{/* {opt.label}*/}
|
||||
{/* </span>*/}
|
||||
|
||||
{/* {selected ? (*/}
|
||||
{/* <span*/}
|
||||
{/* className={classNames(*/}
|
||||
{/* active ? 'text-white' : 'text-indigo-600',*/}
|
||||
{/* 'absolute inset-y-0 right-0 flex items-center pr-4'*/}
|
||||
{/* )}*/}
|
||||
{/* >*/}
|
||||
{/* <CheckIcon className="h-5 w-5" aria-hidden="true"/>*/}
|
||||
{/* </span>*/}
|
||||
{/* ) : null}*/}
|
||||
{/* </>*/}
|
||||
{/* )}*/}
|
||||
{/* </Listbox.Option>*/}
|
||||
{/* ))}*/}
|
||||
{/* </Listbox.Options>*/}
|
||||
{/* </Transition>*/}
|
||||
{/* </div>*/}
|
||||
{/* </div>*/}
|
||||
{/* )}*/}
|
||||
{/* </Listbox>*/}
|
||||
{/* )} />*/}
|
||||
<PasswordFieldWide name="invite_command" label="Invite command" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -295,7 +200,6 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
|
|||
<button
|
||||
type="submit"
|
||||
disabled={pristine || invalid}
|
||||
// className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
className={classNames(pristine || invalid ? "bg-indigo-300" : "bg-indigo-600 hover:bg-indigo-700","inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500")}
|
||||
>
|
||||
Create
|
||||
|
|
|
@ -1,34 +1,22 @@
|
|||
import {Fragment, useEffect, useRef} from "react";
|
||||
import {useMutation} from "react-query";
|
||||
import {Network} from "../../domain/interfaces";
|
||||
import {Dialog, Transition} from "@headlessui/react";
|
||||
import {XIcon} from "@heroicons/react/solid";
|
||||
import {Field, Form} from "react-final-form";
|
||||
import { Fragment, useEffect, useRef } from "react";
|
||||
import { useMutation } from "react-query";
|
||||
import { Network } from "../../domain/interfaces";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { XIcon } from "@heroicons/react/solid";
|
||||
import { Field, Form } from "react-final-form";
|
||||
import DEBUG from "../../components/debug";
|
||||
import {SwitchGroup, TextAreaWide, TextFieldWide} from "../../components/inputs";
|
||||
import {queryClient} from "../../App";
|
||||
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
|
||||
import { queryClient } from "../../App";
|
||||
|
||||
import arrayMutators from "final-form-arrays";
|
||||
import { FieldArray } from "react-final-form-arrays";
|
||||
import {classNames} from "../../styles/utils";
|
||||
import {useToggle} from "../../hooks/hooks";
|
||||
import {DeleteModal} from "../../components/modals";
|
||||
import { classNames } from "../../styles/utils";
|
||||
import { useToggle } from "../../hooks/hooks";
|
||||
import { DeleteModal } from "../../components/modals";
|
||||
import APIClient from "../../api/APIClient";
|
||||
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
|
||||
|
||||
|
||||
// interface radioFieldsetOption {
|
||||
// label: string;
|
||||
// description: string;
|
||||
// value: string;
|
||||
// }
|
||||
//
|
||||
// const saslTypeOptions: radioFieldsetOption[] = [
|
||||
// {label: "None", description: "None", value: ""},
|
||||
// {label: "Plain", description: "SASL plain", value: "PLAIN"},
|
||||
// {label: "NickServ", description: "/NS identify", value: "NICKSERV"},
|
||||
// ];
|
||||
|
||||
function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
||||
function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
|
||||
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false)
|
||||
|
||||
const mutation = useMutation((network: Network) => APIClient.irc.updateNetwork(network), {
|
||||
|
@ -59,7 +47,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
|||
// let cmds = data.connect_commands && data.connect_commands.length > 0 ? data.connect_commands.replace(/\r\n/g,"\n").split("\n") : [];
|
||||
// data.connect_commands = cmds
|
||||
// console.log("formatted", data)
|
||||
|
||||
|
||||
mutation.mutate(data)
|
||||
};
|
||||
|
||||
|
@ -70,12 +58,16 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
|||
errors.name = "Required";
|
||||
}
|
||||
|
||||
if (!values.addr) {
|
||||
errors.addr = "Required";
|
||||
if (!values.server) {
|
||||
errors.server = "Required";
|
||||
}
|
||||
|
||||
if (!values.nick) {
|
||||
errors.nick = "Required";
|
||||
if (!values.port) {
|
||||
errors.port = "Required";
|
||||
}
|
||||
|
||||
if (!values.nickserv.account) {
|
||||
errors.nickserv.account = "Required";
|
||||
}
|
||||
|
||||
return errors;
|
||||
|
@ -98,7 +90,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
|||
text="Are you sure you want to remove this network and channels? This action cannot be undone."
|
||||
/>
|
||||
<div className="absolute inset-0 overflow-hidden">
|
||||
<Dialog.Overlay className="absolute inset-0"/>
|
||||
<Dialog.Overlay className="absolute inset-0" />
|
||||
|
||||
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
|
||||
<Transition.Child
|
||||
|
@ -117,19 +109,13 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
|||
id: network.id,
|
||||
name: network.name,
|
||||
enabled: network.enabled,
|
||||
addr: network.addr,
|
||||
server: network.server,
|
||||
port: network.port,
|
||||
tls: network.tls,
|
||||
nick: network.nick,
|
||||
nickserv: network.nickserv,
|
||||
pass: network.pass,
|
||||
invite_command: network.invite_command,
|
||||
connect_commands: network.connect_commands,
|
||||
sasl: network.sasl,
|
||||
// sasl: {
|
||||
// mechanism: network.sasl.mechanism,
|
||||
// plain: {
|
||||
// username: network.sasl.plain.username,
|
||||
// password: network.sasl.plain.password,
|
||||
// }
|
||||
// },
|
||||
channels: network.channels
|
||||
}}
|
||||
mutators={{
|
||||
|
@ -138,10 +124,10 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
|||
validate={validate}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({handleSubmit, values, pristine, invalid}) => {
|
||||
{({ handleSubmit, values, pristine, invalid }) => {
|
||||
return (
|
||||
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
|
||||
onSubmit={handleSubmit}>
|
||||
onSubmit={handleSubmit}>
|
||||
<div className="flex-1">
|
||||
{/* Header */}
|
||||
<div className="px-4 py-6 bg-gray-50 sm:px-6">
|
||||
|
@ -160,106 +146,48 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
|||
onClick={toggle}
|
||||
>
|
||||
<span className="sr-only">Close panel</span>
|
||||
<XIcon className="h-6 w-6" aria-hidden="true"/>
|
||||
<XIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<TextFieldWide name="name" label="Name" placeholder="Name" required={true} />
|
||||
|
||||
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
|
||||
<div
|
||||
className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<SwitchGroup name="enabled" label="Enabled"/>
|
||||
<SwitchGroup name="enabled" label="Enabled" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
<TextFieldWide name="addr" label="Address" placeholder="Address:port eg irc.server.net:6697" required={true} />
|
||||
<div className="px-6 space-y-1 mt-6">
|
||||
<Dialog.Title className="text-lg font-medium text-gray-900">Connection</Dialog.Title>
|
||||
{/* <p className="text-sm text-gray-500">
|
||||
Networks, channels and invite commands are configured automatically.
|
||||
</p> */}
|
||||
</div>
|
||||
<TextFieldWide name="server" label="Server" placeholder="Address: Eg irc.server.net" required={true} />
|
||||
<NumberFieldWide name="port" label="Port" />
|
||||
|
||||
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
|
||||
<SwitchGroup name="tls" label="TLS"/>
|
||||
<SwitchGroup name="tls" label="TLS" />
|
||||
</div>
|
||||
|
||||
<TextFieldWide name="nick" label="Nick" placeholder="Nick" required={true} />
|
||||
<PasswordFieldWide name="pass" label="Password" help="Network password" />
|
||||
|
||||
<TextFieldWide name="password" label="Password" placeholder="Network password" />
|
||||
<div className="px-6 space-y-1 border-t pt-6">
|
||||
<Dialog.Title className="text-lg font-medium text-gray-900">Account</Dialog.Title>
|
||||
{/* <p className="text-sm text-gray-500">
|
||||
Networks, channels and invite commands are configured automatically.
|
||||
</p> */}
|
||||
</div>
|
||||
|
||||
<TextAreaWide name="connect_commands" label="Connect commands" placeholder="/msg test this" />
|
||||
<TextFieldWide name="nickserv.account" label="NickServ Account" required={true} />
|
||||
<PasswordFieldWide name="nickserv.password" label="NickServ Password" />
|
||||
|
||||
|
||||
{/* <Field*/}
|
||||
{/* name="sasl.mechanism"*/}
|
||||
{/* type="select"*/}
|
||||
{/* render={({input}) => (*/}
|
||||
{/* <Listbox value={input.value} onChange={input.onChange}>*/}
|
||||
{/* {({open}) => (*/}
|
||||
{/* <div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">*/}
|
||||
{/* <div>*/}
|
||||
{/* <Listbox.Label className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2">SASL / auth</Listbox.Label>*/}
|
||||
{/* </div>*/}
|
||||
{/* <div className="sm:col-span-2 relative">*/}
|
||||
{/* <Listbox.Button*/}
|
||||
{/* className="bg-white relative w-full border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">*/}
|
||||
{/* <span className="block truncate">{input.value ? saslTypeOptions.find(c => c.value === input.value)!.label : "Choose a auth type"}</span>*/}
|
||||
{/* /!*<span className="block truncate">Choose a auth type</span>*!/*/}
|
||||
{/* <span*/}
|
||||
{/* className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">*/}
|
||||
{/* <SelectorIcon className="h-5 w-5 text-gray-400" aria-hidden="true"/>*/}
|
||||
{/*</span>*/}
|
||||
{/* </Listbox.Button>*/}
|
||||
|
||||
{/* <Transition*/}
|
||||
{/* show={open}*/}
|
||||
{/* as={Fragment}*/}
|
||||
{/* leave="transition ease-in duration-100"*/}
|
||||
{/* leaveFrom="opacity-100"*/}
|
||||
{/* leaveTo="opacity-0"*/}
|
||||
{/* >*/}
|
||||
{/* <Listbox.Options*/}
|
||||
{/* static*/}
|
||||
{/* className="absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"*/}
|
||||
{/* >*/}
|
||||
{/* {saslTypeOptions.map((opt: any) => (*/}
|
||||
{/* <Listbox.Option*/}
|
||||
{/* key={opt.value}*/}
|
||||
{/* className={({active}) =>*/}
|
||||
{/* classNames(*/}
|
||||
{/* active ? 'text-white bg-indigo-600' : 'text-gray-900',*/}
|
||||
{/* 'cursor-default select-none relative py-2 pl-3 pr-9'*/}
|
||||
{/* )*/}
|
||||
{/* }*/}
|
||||
{/* value={opt.value}*/}
|
||||
{/* >*/}
|
||||
{/* {({selected, active}) => (*/}
|
||||
{/* <>*/}
|
||||
{/* <span className={classNames(selected ? 'font-semibold' : 'font-normal', 'block truncate')}>*/}
|
||||
{/* {opt.label}*/}
|
||||
{/* </span>*/}
|
||||
|
||||
{/* {selected ? (*/}
|
||||
{/* <span*/}
|
||||
{/* className={classNames(*/}
|
||||
{/* active ? 'text-white' : 'text-indigo-600',*/}
|
||||
{/* 'absolute inset-y-0 right-0 flex items-center pr-4'*/}
|
||||
{/* )}*/}
|
||||
{/* >*/}
|
||||
{/* <CheckIcon className="h-5 w-5" aria-hidden="true"/>*/}
|
||||
{/* </span>*/}
|
||||
{/* ) : null}*/}
|
||||
{/* </>*/}
|
||||
{/* )}*/}
|
||||
{/* </Listbox.Option>*/}
|
||||
{/* ))}*/}
|
||||
{/* </Listbox.Options>*/}
|
||||
{/* </Transition>*/}
|
||||
{/* </div>*/}
|
||||
{/* </div>*/}
|
||||
{/* )}*/}
|
||||
{/* </Listbox>*/}
|
||||
{/* )} />*/}
|
||||
<PasswordFieldWide name="invite_command" label="Invite command" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -294,7 +222,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
|||
onClick={() => fields.remove(index)}
|
||||
>
|
||||
<span className="sr-only">Remove</span>
|
||||
<XIcon className="h-6 w-6" aria-hidden="true"/>
|
||||
<XIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
|
@ -337,7 +265,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
|||
<button
|
||||
type="submit"
|
||||
disabled={pristine || invalid}
|
||||
className={classNames(pristine || invalid ? "bg-indigo-300" : "bg-indigo-600 hover:bg-indigo-700","inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500")}
|
||||
className={classNames(pristine || invalid ? "bg-indigo-300" : "bg-indigo-600 hover:bg-indigo-700", "inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500")}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
|
@ -345,27 +273,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/*<div*/}
|
||||
{/* className="flex-shrink-0 px-4 border-t border-gray-200 py-5 sm:px-6">*/}
|
||||
{/* <div className="space-x-3 flex justify-end">*/}
|
||||
{/* <button*/}
|
||||
{/* type="button"*/}
|
||||
{/* className="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"*/}
|
||||
{/* onClick={toggle}*/}
|
||||
{/* >*/}
|
||||
{/* Cancel*/}
|
||||
{/* </button>*/}
|
||||
{/* <button*/}
|
||||
{/* type="submit"*/}
|
||||
{/* disabled={pristine || invalid}*/}
|
||||
{/* className={classNames(pristine || invalid ? "bg-indigo-300" : "bg-indigo-600 hover:bg-indigo-700","inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500")}*/}
|
||||
{/* >*/}
|
||||
{/* Save*/}
|
||||
{/* </button>*/}
|
||||
{/* </div>*/}
|
||||
{/*</div>*/}
|
||||
|
||||
<DEBUG values={values}/>
|
||||
<DEBUG values={values} />
|
||||
</form>
|
||||
)
|
||||
}}
|
||||
|
|
|
@ -82,7 +82,7 @@ function IrcSettings() {
|
|||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Addr
|
||||
Server
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
|
@ -138,8 +138,8 @@ const ListItem = ({ idx, network }: any) => {
|
|||
</Switch>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{network.name}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{network.addr} {network.tls && <span className="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">TLS</span>}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{network.nick}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{network.server}:{network.port} {network.tls && <span className="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">TLS</span>}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{network.nickserv?.account}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<span className="text-indigo-600 hover:text-indigo-900 cursor-pointer" onClick={toggleUpdate}>
|
||||
Edit
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue