mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 16:59:12 +00:00
refactor(http): implement bufio (#1604)
* fix: misc http fixes * feat(io): implement bufio around syscalls * peek-a-boo * this can't be right. * you better be wearing a helmet * jesus christ. * refactor(notifications): check err on non-ok status * fix(notifications): add missing name method * refactor(indexer): api clients * fix(indexer): ptp test --------- Co-authored-by: ze0s <ze0s@riseup.net>
This commit is contained in:
parent
d13b421c42
commit
cc0cca9f0d
22 changed files with 465 additions and 304 deletions
|
@ -86,8 +86,7 @@ func (s *service) porla(ctx context.Context, action *domain.Action, release doma
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
reader := bufio.NewReader(file)
|
content, err := io.ReadAll(bufio.NewReader(file))
|
||||||
content, err := io.ReadAll(reader)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to read file: %s", release.TorrentTmpFile)
|
return nil, errors.Wrap(err, "failed to read file: %s", release.TorrentTmpFile)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ type NotificationRepo interface {
|
||||||
type NotificationSender interface {
|
type NotificationSender interface {
|
||||||
Send(event NotificationEvent, payload NotificationPayload) error
|
Send(event NotificationEvent, payload NotificationPayload) error
|
||||||
CanSend(event NotificationEvent) bool
|
CanSend(event NotificationEvent) bool
|
||||||
|
Name() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -535,7 +536,7 @@ func (r *Release) downloadTorrentFile(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the body into bytes
|
// Read the body into bytes
|
||||||
bodyBytes, err := io.ReadAll(resp.Body)
|
bodyBytes, err := io.ReadAll(bufio.NewReader(resp.Body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "error reading response body")
|
return errors.Wrap(err, "error reading response body")
|
||||||
}
|
}
|
||||||
|
|
|
@ -773,13 +773,15 @@ func (s *service) webhook(ctx context.Context, external domain.FilterExternal, r
|
||||||
|
|
||||||
s.log.Debug().Msgf("filter external webhook response status: %d", res.StatusCode)
|
s.log.Debug().Msgf("filter external webhook response status: %d", res.StatusCode)
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
if s.log.Debug().Enabled() {
|
||||||
if err != nil {
|
body, err := io.ReadAll(res.Body)
|
||||||
return res.StatusCode, errors.Wrap(err, "could not read request body")
|
if err != nil {
|
||||||
}
|
return res.StatusCode, errors.Wrap(err, "could not read request body")
|
||||||
|
}
|
||||||
|
|
||||||
if len(body) > 0 {
|
if len(body) > 0 {
|
||||||
s.log.Debug().Msgf("filter external webhook response status: %d body: %s", res.StatusCode, body)
|
s.log.Debug().Msgf("filter external webhook response status: %d body: %s", res.StatusCode, body)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if utils.StrSliceContains(retryStatusCodes, strconv.Itoa(res.StatusCode)) {
|
if utils.StrSliceContains(retryStatusCodes, strconv.Itoa(res.StatusCode)) {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package notification
|
package notification
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -54,6 +55,10 @@ type discordSender struct {
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *discordSender) Name() string {
|
||||||
|
return "discord"
|
||||||
|
}
|
||||||
|
|
||||||
func NewDiscordSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender {
|
func NewDiscordSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender {
|
||||||
return &discordSender{
|
return &discordSender{
|
||||||
log: log.With().Str("sender", "discord").Logger(),
|
log: log.With().Str("sender", "discord").Logger(),
|
||||||
|
@ -73,14 +78,12 @@ func (a *discordSender) Send(event domain.NotificationEvent, payload domain.Noti
|
||||||
|
|
||||||
jsonData, err := json.Marshal(m)
|
jsonData, err := json.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.log.Error().Err(err).Msgf("discord client could not marshal data: %v", m)
|
return errors.Wrap(err, "could not marshal json request for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not marshal data: %+v", m)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodPost, a.Settings.Webhook, bytes.NewBuffer(jsonData))
|
req, err := http.NewRequest(http.MethodPost, a.Settings.Webhook, bytes.NewBuffer(jsonData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.log.Error().Err(err).Msgf("discord client request error: %v", event)
|
return errors.Wrap(err, "could not create request for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not create request")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
@ -88,24 +91,21 @@ func (a *discordSender) Send(event domain.NotificationEvent, payload domain.Noti
|
||||||
|
|
||||||
res, err := a.httpClient.Do(req)
|
res, err := a.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.log.Error().Err(err).Msgf("discord client request error: %v", event)
|
return errors.Wrap(err, "client request error for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not make request: %+v", req)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
a.log.Trace().Msgf("discord response status: %d", res.StatusCode)
|
||||||
if err != nil {
|
|
||||||
a.log.Error().Err(err).Msgf("discord client request error: %v", event)
|
|
||||||
return errors.Wrap(err, "could not read data")
|
|
||||||
}
|
|
||||||
|
|
||||||
a.log.Trace().Msgf("discord status: %v response: %v", res.StatusCode, string(body))
|
|
||||||
|
|
||||||
// discord responds with 204, Notifiarr with 204 so lets take all 200 as ok
|
// discord responds with 204, Notifiarr with 204 so lets take all 200 as ok
|
||||||
if res.StatusCode >= 300 {
|
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusNoContent {
|
||||||
a.log.Error().Err(err).Msgf("discord client request error: %v", string(body))
|
body, err := io.ReadAll(bufio.NewReader(res.Body))
|
||||||
return errors.New("bad status: %v body: %v", res.StatusCode, string(body))
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not read body for event: %v payload: %v", event, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("unexpected status: %v body: %v", res.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
a.log.Debug().Msg("notification successfully sent to discord")
|
a.log.Debug().Msg("notification successfully sent to discord")
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package notification
|
package notification
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -31,6 +32,10 @@ type gotifySender struct {
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *gotifySender) Name() string {
|
||||||
|
return "gotify"
|
||||||
|
}
|
||||||
|
|
||||||
func NewGotifySender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender {
|
func NewGotifySender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender {
|
||||||
return &gotifySender{
|
return &gotifySender{
|
||||||
log: log.With().Str("sender", "gotify").Logger(),
|
log: log.With().Str("sender", "gotify").Logger(),
|
||||||
|
@ -53,11 +58,9 @@ func (s *gotifySender) Send(event domain.NotificationEvent, payload domain.Notif
|
||||||
data.Set("message", m.Message)
|
data.Set("message", m.Message)
|
||||||
data.Set("title", m.Title)
|
data.Set("title", m.Title)
|
||||||
|
|
||||||
url := fmt.Sprintf("%v/message?token=%v", s.Settings.Host, s.Settings.Token)
|
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%v/message?token=%v", s.Settings.Host, s.Settings.Token), strings.NewReader(data.Encode()))
|
||||||
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(data.Encode()))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error().Err(err).Msgf("gotify client request error: %v", event)
|
return errors.Wrap(err, "could not create request for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not create request")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
@ -65,23 +68,20 @@ func (s *gotifySender) Send(event domain.NotificationEvent, payload domain.Notif
|
||||||
|
|
||||||
res, err := s.httpClient.Do(req)
|
res, err := s.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error().Err(err).Msgf("gotify client request error: %v", event)
|
return errors.Wrap(err, "client request error for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not make request: %+v", req)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
s.log.Trace().Msgf("gotify status: %d", res.StatusCode)
|
||||||
if err != nil {
|
|
||||||
s.log.Error().Err(err).Msgf("gotify client request error: %v", event)
|
|
||||||
return errors.Wrap(err, "could not read data")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Trace().Msgf("gotify status: %v response: %v", res.StatusCode, string(body))
|
|
||||||
|
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
s.log.Error().Err(err).Msgf("gotify client request error: %v", string(body))
|
body, err := io.ReadAll(bufio.NewReader(res.Body))
|
||||||
return errors.New("bad status: %v body: %v", res.StatusCode, string(body))
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not read body for event: %v payload: %v", event, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("unexpected status: %v body: %v", res.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log.Debug().Msg("notification successfully sent to gotify")
|
s.log.Debug().Msg("notification successfully sent to gotify")
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package notification
|
package notification
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
@ -31,6 +33,10 @@ type lunaSeaSender struct {
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *lunaSeaSender) Name() string {
|
||||||
|
return "lunasea"
|
||||||
|
}
|
||||||
|
|
||||||
func (s *lunaSeaSender) rewriteWebhookURL(url string) string {
|
func (s *lunaSeaSender) rewriteWebhookURL(url string) string {
|
||||||
re := regexp.MustCompile(`/(radarr|sonarr|lidarr|tautulli|overseerr)/`)
|
re := regexp.MustCompile(`/(radarr|sonarr|lidarr|tautulli|overseerr)/`)
|
||||||
return re.ReplaceAllString(url, "/custom/")
|
return re.ReplaceAllString(url, "/custom/")
|
||||||
|
@ -57,31 +63,32 @@ func (s *lunaSeaSender) Send(event domain.NotificationEvent, payload domain.Noti
|
||||||
|
|
||||||
jsonData, err := json.Marshal(m)
|
jsonData, err := json.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error().Err(err).Msg("lunasea client could not marshal data")
|
return errors.Wrap(err, "could not marshal json request for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not marshal data")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rewrittenURL := s.rewriteWebhookURL(s.Settings.Webhook)
|
rewrittenURL := s.rewriteWebhookURL(s.Settings.Webhook)
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodPost, rewrittenURL, bytes.NewBuffer(jsonData))
|
req, err := http.NewRequest(http.MethodPost, rewrittenURL, bytes.NewBuffer(jsonData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error().Err(err).Msg("lunasea client request error")
|
return errors.Wrap(err, "could not create request for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not create request")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
res, err := s.httpClient.Do(req)
|
res, err := s.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error().Err(err).Msg("lunasea client request error")
|
return errors.Wrap(err, "client request error for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not make request")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
if res.StatusCode >= 300 {
|
if res.StatusCode != http.StatusOK {
|
||||||
s.log.Error().Msgf("bad status from lunasea: %v", res.StatusCode)
|
body, err := io.ReadAll(bufio.NewReader(res.Body))
|
||||||
return errors.New("bad status: %v", res.StatusCode)
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not read body for event: %v payload: %v", event, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("unexpected status: %v body: %v", res.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log.Debug().Msg("notification successfully sent to lunasea")
|
s.log.Debug().Msg("notification successfully sent to lunasea")
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package notification
|
package notification
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
@ -49,6 +50,10 @@ type notifiarrSender struct {
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *notifiarrSender) Name() string {
|
||||||
|
return "notifiarr"
|
||||||
|
}
|
||||||
|
|
||||||
func NewNotifiarrSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender {
|
func NewNotifiarrSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender {
|
||||||
return ¬ifiarrSender{
|
return ¬ifiarrSender{
|
||||||
log: log.With().Str("sender", "notifiarr").Logger(),
|
log: log.With().Str("sender", "notifiarr").Logger(),
|
||||||
|
@ -69,14 +74,12 @@ func (s *notifiarrSender) Send(event domain.NotificationEvent, payload domain.No
|
||||||
|
|
||||||
jsonData, err := json.Marshal(m)
|
jsonData, err := json.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error().Err(err).Msgf("notifiarr client could not marshal data: %v", m)
|
return errors.Wrap(err, "could not marshal json request for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not marshal data: %+v", m)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodPost, s.baseUrl, bytes.NewBuffer(jsonData))
|
req, err := http.NewRequest(http.MethodPost, s.baseUrl, bytes.NewBuffer(jsonData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error().Err(err).Msgf("notifiarr client request error: %v", event)
|
return errors.Wrap(err, "could not create request for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not create request")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
@ -85,23 +88,20 @@ func (s *notifiarrSender) Send(event domain.NotificationEvent, payload domain.No
|
||||||
|
|
||||||
res, err := s.httpClient.Do(req)
|
res, err := s.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error().Err(err).Msgf("notifiarr client request error: %v", event)
|
return errors.Wrap(err, "client request error for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not make request: %+v", req)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
s.log.Trace().Msgf("response status: %d", res.StatusCode)
|
||||||
if err != nil {
|
|
||||||
s.log.Error().Err(err).Msgf("notifiarr client request error: %v", event)
|
|
||||||
return errors.Wrap(err, "could not read data")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Trace().Msgf("notifiarr status: %v response: %v", res.StatusCode, string(body))
|
|
||||||
|
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
s.log.Error().Err(err).Msgf("notifiarr client request error: %v", string(body))
|
body, err := io.ReadAll(bufio.NewReader(res.Body))
|
||||||
return errors.New("bad status: %v body: %v", res.StatusCode, string(body))
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not read body for event: %v payload: %v", event, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("unexpected status: %v body: %v", res.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log.Debug().Msg("notification successfully sent to notifiarr")
|
s.log.Debug().Msg("notification successfully sent to notifiarr")
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package notification
|
package notification
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -30,6 +31,10 @@ type ntfySender struct {
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ntfySender) Name() string {
|
||||||
|
return "ntfy"
|
||||||
|
}
|
||||||
|
|
||||||
func NewNtfySender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender {
|
func NewNtfySender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender {
|
||||||
return &ntfySender{
|
return &ntfySender{
|
||||||
log: log.With().Str("sender", "ntfy").Logger(),
|
log: log.With().Str("sender", "ntfy").Logger(),
|
||||||
|
@ -50,8 +55,7 @@ func (s *ntfySender) Send(event domain.NotificationEvent, payload domain.Notific
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodPost, s.Settings.Host, strings.NewReader(m.Message))
|
req, err := http.NewRequest(http.MethodPost, s.Settings.Host, strings.NewReader(m.Message))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error().Err(err).Msgf("ntfy client request error: %v", event)
|
return errors.Wrap(err, "could not create request for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not create request")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "text/plain")
|
req.Header.Set("Content-Type", "text/plain")
|
||||||
|
@ -71,23 +75,20 @@ func (s *ntfySender) Send(event domain.NotificationEvent, payload domain.Notific
|
||||||
|
|
||||||
res, err := s.httpClient.Do(req)
|
res, err := s.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error().Err(err).Msgf("ntfy client request error: %v", event)
|
return errors.Wrap(err, "client request error for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not make request: %+v", req)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
s.log.Trace().Msgf("ntfy response status: %d", res.StatusCode)
|
||||||
if err != nil {
|
|
||||||
s.log.Error().Err(err).Msgf("ntfy client request error: %v", event)
|
|
||||||
return errors.Wrap(err, "could not read data")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Trace().Msgf("ntfy status: %v response: %v", res.StatusCode, string(body))
|
|
||||||
|
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
s.log.Error().Err(err).Msgf("ntfy client request error: %v", string(body))
|
body, err := io.ReadAll(bufio.NewReader(res.Body))
|
||||||
return errors.New("bad status: %v body: %v", res.StatusCode, string(body))
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not read body for event: %v payload: %v", event, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("unexpected status: %v body: %v", res.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log.Debug().Msg("notification successfully sent to ntfy")
|
s.log.Debug().Msg("notification successfully sent to ntfy")
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package notification
|
package notification
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -38,6 +39,10 @@ type pushoverSender struct {
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *pushoverSender) Name() string {
|
||||||
|
return "pushover"
|
||||||
|
}
|
||||||
|
|
||||||
func NewPushoverSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender {
|
func NewPushoverSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender {
|
||||||
return &pushoverSender{
|
return &pushoverSender{
|
||||||
log: log.With().Str("sender", "pushover").Logger(),
|
log: log.With().Str("sender", "pushover").Logger(),
|
||||||
|
@ -81,8 +86,7 @@ func (s *pushoverSender) Send(event domain.NotificationEvent, payload domain.Not
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodPost, s.baseUrl, strings.NewReader(data.Encode()))
|
req, err := http.NewRequest(http.MethodPost, s.baseUrl, strings.NewReader(data.Encode()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error().Err(err).Msgf("pushover client request error: %v", event)
|
return errors.Wrap(err, "could not create request for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not create request")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
@ -90,23 +94,20 @@ func (s *pushoverSender) Send(event domain.NotificationEvent, payload domain.Not
|
||||||
|
|
||||||
res, err := s.httpClient.Do(req)
|
res, err := s.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error().Err(err).Msgf("pushover client request error: %v", event)
|
return errors.Wrap(err, "client request error for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not make request: %+v", req)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
s.log.Trace().Msgf("pushover response status: %d", res.StatusCode)
|
||||||
if err != nil {
|
|
||||||
s.log.Error().Err(err).Msgf("pushover client request error: %v", event)
|
|
||||||
return errors.Wrap(err, "could not read data")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Trace().Msgf("pushover status: %v response: %v", res.StatusCode, string(body))
|
|
||||||
|
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
s.log.Error().Err(err).Msgf("pushover client request error: %v", string(body))
|
body, err := io.ReadAll(bufio.NewReader(res.Body))
|
||||||
return errors.New("bad status: %v body: %v", res.StatusCode, string(body))
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not read body for event: %v payload: %v", event, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("unexpected status: %v body: %v", res.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log.Debug().Msg("notification successfully sent to pushover")
|
s.log.Debug().Msg("notification successfully sent to pushover")
|
||||||
|
|
|
@ -155,7 +155,9 @@ func (s *service) Send(event domain.NotificationEvent, payload domain.Notificati
|
||||||
for _, sender := range s.senders {
|
for _, sender := range s.senders {
|
||||||
// check if sender is active and have notification types
|
// check if sender is active and have notification types
|
||||||
if sender.CanSend(event) {
|
if sender.CanSend(event) {
|
||||||
sender.Send(event, payload)
|
if err := sender.Send(event, payload); err != nil {
|
||||||
|
s.log.Error().Err(err).Msgf("could not send %s notification for %v", sender.Name(), string(event))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
|
@ -13,6 +13,10 @@ type shoutrrrSender struct {
|
||||||
builder MessageBuilderPlainText
|
builder MessageBuilderPlainText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *shoutrrrSender) Name() string {
|
||||||
|
return "shoutrrr"
|
||||||
|
}
|
||||||
|
|
||||||
func NewShoutrrrSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender {
|
func NewShoutrrrSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender {
|
||||||
return &shoutrrrSender{
|
return &shoutrrrSender{
|
||||||
log: log.With().Str("sender", "shoutrrr").Logger(),
|
log: log.With().Str("sender", "shoutrrr").Logger(),
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package notification
|
package notification
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -36,6 +37,10 @@ type telegramSender struct {
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *telegramSender) Name() string {
|
||||||
|
return "telegram"
|
||||||
|
}
|
||||||
|
|
||||||
func NewTelegramSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender {
|
func NewTelegramSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender {
|
||||||
threadID := 0
|
threadID := 0
|
||||||
if t := settings.Topic; t != "" {
|
if t := settings.Topic; t != "" {
|
||||||
|
@ -69,8 +74,7 @@ func (s *telegramSender) Send(event domain.NotificationEvent, payload domain.Not
|
||||||
|
|
||||||
jsonData, err := json.Marshal(m)
|
jsonData, err := json.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error().Err(err).Msgf("telegram client could not marshal data: %v", m)
|
return errors.Wrap(err, "could not marshal json request for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not marshal data: %+v", m)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var host string
|
var host string
|
||||||
|
@ -85,8 +89,7 @@ func (s *telegramSender) Send(event domain.NotificationEvent, payload domain.Not
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonData))
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error().Err(err).Msgf("telegram client request error: %v", event)
|
return errors.Wrap(err, "could not create request for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not create request")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
@ -94,26 +97,24 @@ func (s *telegramSender) Send(event domain.NotificationEvent, payload domain.Not
|
||||||
|
|
||||||
res, err := s.httpClient.Do(req)
|
res, err := s.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error().Err(err).Msgf("telegram client request error: %v", event)
|
return errors.Wrap(err, "client request error for event: %v payload: %v", event, payload)
|
||||||
return errors.Wrap(err, "could not make request: %+v", req)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
s.log.Trace().Msgf("telegram status: %d", res.StatusCode)
|
||||||
if err != nil {
|
|
||||||
s.log.Error().Err(err).Msgf("telegram client request error: %v", event)
|
|
||||||
return errors.Wrap(err, "could not read data")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Trace().Msgf("telegram status: %v response: %v", res.StatusCode, string(body))
|
|
||||||
|
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
s.log.Error().Err(err).Msgf("telegram client request error: %v", string(body))
|
body, err := io.ReadAll(bufio.NewReader(res.Body))
|
||||||
return errors.New("bad status: %v body: %v", res.StatusCode, string(body))
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not read body for event: %v payload: %v", event, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("unexpected status: %v body: %v", res.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log.Debug().Msg("notification successfully sent to telegram")
|
s.log.Debug().Msg("notification successfully sent to telegram")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
108
pkg/ggn/ggn.go
108
pkg/ggn/ggn.go
|
@ -4,10 +4,10 @@
|
||||||
package ggn
|
package ggn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -163,6 +163,13 @@ type Response struct {
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetIndexResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Response struct {
|
||||||
|
ApiVersion string `json:"api_version"`
|
||||||
|
} `json:"response"`
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
err := c.rateLimiter.Wait(ctx) // This is a blocking call. Honors the rate limit
|
err := c.rateLimiter.Wait(ctx) // This is a blocking call. Honors the rate limit
|
||||||
|
@ -201,61 +208,96 @@ func (c *Client) get(ctx context.Context, url string) (*http.Response, error) {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) getJSON(ctx context.Context, params url.Values, data any) error {
|
||||||
|
reqUrl := fmt.Sprintf("%s?%s", c.url, params.Encode())
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, http.NoBody)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "ggn client request error : %s", reqUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("X-API-Key", c.APIKey)
|
||||||
|
req.Header.Set("User-Agent", "autobrr")
|
||||||
|
|
||||||
|
res, err := c.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "ggn client request error : %s", reqUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode == http.StatusUnauthorized {
|
||||||
|
return ErrUnauthorized
|
||||||
|
} else if res.StatusCode == http.StatusForbidden {
|
||||||
|
return ErrForbidden
|
||||||
|
} else if res.StatusCode == http.StatusTooManyRequests {
|
||||||
|
return ErrTooManyRequests
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := bufio.NewReader(res.Body)
|
||||||
|
|
||||||
|
if err := json.NewDecoder(reader).Decode(&data); err != nil {
|
||||||
|
return errors.Wrap(err, "error unmarshal body")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) {
|
func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) {
|
||||||
if torrentID == "" {
|
if torrentID == "" {
|
||||||
return nil, errors.New("ggn client: must have torrentID")
|
return nil, errors.New("ggn client: must have torrentID")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r Response
|
var response *Response
|
||||||
|
|
||||||
v := url.Values{}
|
params := url.Values{}
|
||||||
v.Add("id", torrentID)
|
params.Add("request", "torrent")
|
||||||
params := v.Encode()
|
params.Add("id", torrentID)
|
||||||
|
|
||||||
reqUrl := fmt.Sprintf("%s?%s&%s", c.url, "request=torrent", params)
|
err := c.getJSON(ctx, params, &response)
|
||||||
|
|
||||||
resp, err := c.get(ctx, reqUrl)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "error getting data")
|
return nil, errors.Wrap(err, "error getting data")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
if response.Status != "success" {
|
||||||
|
return nil, errors.New("bad status: %s", response.Status)
|
||||||
body, readErr := io.ReadAll(resp.Body)
|
|
||||||
if readErr != nil {
|
|
||||||
return nil, errors.Wrap(readErr, "error reading body")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = json.Unmarshal(body, &r); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "error unmarshal body")
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Status != "success" {
|
|
||||||
return nil, errors.New("bad status: %s", r.Status)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
t := &domain.TorrentBasic{
|
t := &domain.TorrentBasic{
|
||||||
Id: strconv.Itoa(r.Response.Torrent.Id),
|
Id: strconv.Itoa(response.Response.Torrent.Id),
|
||||||
InfoHash: r.Response.Torrent.InfoHash,
|
InfoHash: response.Response.Torrent.InfoHash,
|
||||||
Size: strconv.FormatUint(r.Response.Torrent.Size, 10),
|
Size: strconv.FormatUint(response.Response.Torrent.Size, 10),
|
||||||
}
|
}
|
||||||
|
|
||||||
return t, nil
|
return t, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestAPI try api access against torrents page
|
// TestAPI try api access against index
|
||||||
func (c *Client) TestAPI(ctx context.Context) (bool, error) {
|
func (c *Client) TestAPI(ctx context.Context) (bool, error) {
|
||||||
resp, err := c.get(ctx, c.url)
|
resp, err := c.GetIndex(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.Wrap(err, "error getting data")
|
return false, errors.Wrap(err, "test api error")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
if resp == nil {
|
||||||
|
return false, nil
|
||||||
if resp.StatusCode == http.StatusOK {
|
|
||||||
return true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetIndex(ctx context.Context) (*GetIndexResponse, error) {
|
||||||
|
var response *GetIndexResponse
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
err := c.getJSON(ctx, params, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error getting data")
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Status != "success" {
|
||||||
|
return nil, errors.New("bad status: %s", response.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,17 +35,26 @@ func Test_client_GetTorrentByID(t *testing.T) {
|
||||||
|
|
||||||
id := r.URL.Query().Get("id")
|
id := r.URL.Query().Get("id")
|
||||||
var jsonPayload []byte
|
var jsonPayload []byte
|
||||||
|
var err error
|
||||||
switch id {
|
switch id {
|
||||||
case "422368":
|
case "422368":
|
||||||
jsonPayload, _ = os.ReadFile("testdata/ggn_get_torrent_by_id.json")
|
jsonPayload, err = os.ReadFile("testdata/ggn_get_torrent_by_id.json")
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
break
|
break
|
||||||
|
|
||||||
case "100002":
|
case "100002":
|
||||||
jsonPayload, _ = os.ReadFile("testdata/ggn_get_torrent_by_id_not_found.json")
|
jsonPayload, err = os.ReadFile("testdata/ggn_get_by_id_not_found.json")
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +111,7 @@ func Test_client_GetTorrentByID(t *testing.T) {
|
||||||
|
|
||||||
got, err := c.GetTorrentByID(context.Background(), tt.args.torrentID)
|
got, err := c.GetTorrentByID(context.Background(), tt.args.torrentID)
|
||||||
if tt.wantErr && assert.Error(t, err) {
|
if tt.wantErr && assert.Error(t, err) {
|
||||||
|
t.Logf("got err: %v", err)
|
||||||
assert.Equal(t, tt.wantErr, err)
|
assert.Equal(t, tt.wantErr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
129
pkg/ops/ops.go
129
pkg/ops/ops.go
|
@ -4,10 +4,10 @@
|
||||||
package ops
|
package ops
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -133,6 +133,44 @@ type Torrent struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetIndexResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Response Response `json:"response"`
|
||||||
|
Info Info `json:"info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Info struct {
|
||||||
|
Source string `json:"source"`
|
||||||
|
Version int64 `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Notifications Notifications `json:"notifications"`
|
||||||
|
Userstats Userstats `json:"userstats"`
|
||||||
|
//Authkey string `json:"authkey"`
|
||||||
|
//Passkey string `json:"passkey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Notifications struct {
|
||||||
|
Messages int64 `json:"messages"`
|
||||||
|
Notifications int64 `json:"notifications"`
|
||||||
|
NewAnnouncement bool `json:"newAnnouncement"`
|
||||||
|
NewBlog bool `json:"newBlog"`
|
||||||
|
NewSubscriptions bool `json:"newSubscriptions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Userstats struct {
|
||||||
|
Uploaded int64 `json:"uploaded"`
|
||||||
|
Downloaded int64 `json:"downloaded"`
|
||||||
|
Ratio float64 `json:"ratio"`
|
||||||
|
Requiredratio int64 `json:"requiredratio"`
|
||||||
|
BonusPoints int64 `json:"bonusPoints"`
|
||||||
|
BonusPointsPerHour float64 `json:"bonusPointsPerHour"`
|
||||||
|
Class string `json:"class"`
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
||||||
//ctx := context.Background()
|
//ctx := context.Background()
|
||||||
err := c.RateLimiter.Wait(req.Context()) // This is a blocking call. Honors the rate limit
|
err := c.RateLimiter.Wait(req.Context()) // This is a blocking call. Honors the rate limit
|
||||||
|
@ -146,14 +184,16 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) get(ctx context.Context, url string) (*http.Response, error) {
|
func (c *Client) getJSON(ctx context.Context, params url.Values, data any) error {
|
||||||
if c.APIKey == "" {
|
if c.APIKey == "" {
|
||||||
return nil, errors.New("orpheus client missing API key!")
|
return errors.New("orpheus client missing API key!")
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
|
reqUrl := fmt.Sprintf("%s?%s", c.url, params.Encode())
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, http.NoBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "could not build request")
|
return errors.Wrap(err, "could not build request")
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Add("Authorization", fmt.Sprintf("token %s", c.APIKey))
|
req.Header.Add("Authorization", fmt.Sprintf("token %s", c.APIKey))
|
||||||
|
@ -161,28 +201,29 @@ func (c *Client) get(ctx context.Context, url string) (*http.Response, error) {
|
||||||
|
|
||||||
res, err := c.Do(req)
|
res, err := c.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, errors.Wrap(err, "could not make request: %+v", req)
|
return errors.Wrap(err, "could not make request: %+v", req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body := bufio.NewReader(res.Body)
|
||||||
|
|
||||||
// return early if not OK
|
// return early if not OK
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
var r ErrorResponse
|
var errResponse ErrorResponse
|
||||||
|
|
||||||
defer res.Body.Close()
|
if err := json.NewDecoder(body).Decode(&errResponse); err != nil {
|
||||||
|
return errors.Wrap(err, "could not unmarshal body")
|
||||||
body, readErr := io.ReadAll(res.Body)
|
|
||||||
if readErr != nil {
|
|
||||||
return nil, errors.Wrap(readErr, "could not read body")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = json.Unmarshal(body, &r); err != nil {
|
return errors.New("status code: %d status: %s error: %s", res.StatusCode, errResponse.Status, errResponse.Error)
|
||||||
return nil, errors.Wrap(err, "could not unmarshal body")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("status code: %d status: %s error: %s", res.StatusCode, r.Status, r.Error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
if err := json.NewDecoder(body).Decode(&data); err != nil {
|
||||||
|
return errors.Wrap(err, "could not unmarshal body")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) {
|
func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) {
|
||||||
|
@ -190,35 +231,22 @@ func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.
|
||||||
return nil, errors.New("orpheus client: must have torrentID")
|
return nil, errors.New("orpheus client: must have torrentID")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r TorrentDetailsResponse
|
var response TorrentDetailsResponse
|
||||||
|
|
||||||
v := url.Values{}
|
params := url.Values{}
|
||||||
v.Add("id", torrentID)
|
params.Add("action", "torrent")
|
||||||
params := v.Encode()
|
params.Add("id", torrentID)
|
||||||
|
|
||||||
reqUrl := fmt.Sprintf("%s?action=torrent&%s", c.url, params)
|
err := c.getJSON(ctx, params, &response)
|
||||||
|
|
||||||
resp, err := c.get(ctx, reqUrl)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "could not get torrent by id: %v", torrentID)
|
return nil, errors.Wrap(err, "could not get torrent by id: %v", torrentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, readErr := io.ReadAll(resp.Body)
|
|
||||||
if readErr != nil {
|
|
||||||
return nil, errors.Wrap(readErr, "could not read body")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(body, &r); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "could not unmarshal body")
|
|
||||||
}
|
|
||||||
|
|
||||||
res := &domain.TorrentBasic{
|
res := &domain.TorrentBasic{
|
||||||
Id: strconv.Itoa(r.Response.Torrent.Id),
|
Id: strconv.Itoa(response.Response.Torrent.Id),
|
||||||
InfoHash: r.Response.Torrent.InfoHash,
|
InfoHash: response.Response.Torrent.InfoHash,
|
||||||
Size: strconv.Itoa(r.Response.Torrent.Size),
|
Size: strconv.Itoa(response.Response.Torrent.Size),
|
||||||
Uploader: r.Response.Torrent.Username,
|
Uploader: response.Response.Torrent.Username,
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
|
@ -226,16 +254,29 @@ func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.
|
||||||
|
|
||||||
// TestAPI try api access against torrents page
|
// TestAPI try api access against torrents page
|
||||||
func (c *Client) TestAPI(ctx context.Context) (bool, error) {
|
func (c *Client) TestAPI(ctx context.Context) (bool, error) {
|
||||||
resp, err := c.get(ctx, c.url+"?action=index")
|
resp, err := c.GetIndex(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.Wrap(err, "test api error")
|
return false, errors.Wrap(err, "test api error")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
if resp == nil {
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetIndex get API index
|
||||||
|
func (c *Client) GetIndex(ctx context.Context) (*GetIndexResponse, error) {
|
||||||
|
var response GetIndexResponse
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("action", "index")
|
||||||
|
|
||||||
|
err := c.getJSON(ctx, params, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "test api error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
109
pkg/ptp/ptp.go
109
pkg/ptp/ptp.go
|
@ -4,10 +4,10 @@
|
||||||
package ptp
|
package ptp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
@ -80,6 +80,7 @@ type TorrentResponse struct {
|
||||||
ImdbVoteCount int `json:"ImdbVoteCount"`
|
ImdbVoteCount int `json:"ImdbVoteCount"`
|
||||||
Torrents []Torrent `json:"Torrents"`
|
Torrents []Torrent `json:"Torrents"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Torrent struct {
|
type Torrent struct {
|
||||||
Id string `json:"Id"`
|
Id string `json:"Id"`
|
||||||
InfoHash string `json:"InfoHash"`
|
InfoHash string `json:"InfoHash"`
|
||||||
|
@ -98,7 +99,9 @@ type Torrent struct {
|
||||||
ReleaseGroup *string `json:"ReleaseGroup"`
|
ReleaseGroup *string `json:"ReleaseGroup"`
|
||||||
Checked bool `json:"Checked"`
|
Checked bool `json:"Checked"`
|
||||||
GoldenPopcorn bool `json:"GoldenPopcorn"`
|
GoldenPopcorn bool `json:"GoldenPopcorn"`
|
||||||
RemasterTitle string `json:"RemasterTitle,omitempty"`
|
FreeleechType *string `json:"FreeleechType,omitempty"`
|
||||||
|
RemasterTitle *string `json:"RemasterTitle,omitempty"`
|
||||||
|
RemasterYear *string `json:"RemasterYear,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
@ -114,10 +117,12 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) get(ctx context.Context, url string) (*http.Response, error) {
|
func (c *Client) getJSON(ctx context.Context, params url.Values, data any) error {
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
|
reqUrl := fmt.Sprintf("%s?%s", c.url, params.Encode())
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, http.NoBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "ptp client request error : %v", url)
|
return errors.Wrap(err, "ptp client request error : %v", reqUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Add("ApiUser", c.APIUser)
|
req.Header.Add("ApiUser", c.APIUser)
|
||||||
|
@ -126,18 +131,26 @@ func (c *Client) get(ctx context.Context, url string) (*http.Response, error) {
|
||||||
|
|
||||||
res, err := c.Do(req)
|
res, err := c.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, errors.Wrap(err, "ptp client request error : %v", url)
|
return errors.Wrap(err, "ptp client request error : %v", reqUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
if res.StatusCode == http.StatusUnauthorized {
|
if res.StatusCode == http.StatusUnauthorized {
|
||||||
return res, ErrUnauthorized
|
return ErrUnauthorized
|
||||||
} else if res.StatusCode == http.StatusForbidden {
|
} else if res.StatusCode == http.StatusForbidden {
|
||||||
return res, ErrForbidden
|
return ErrForbidden
|
||||||
} else if res.StatusCode == http.StatusTooManyRequests {
|
} else if res.StatusCode == http.StatusTooManyRequests {
|
||||||
return res, ErrTooManyRequests
|
return ErrTooManyRequests
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
body := bufio.NewReader(res.Body)
|
||||||
|
|
||||||
|
if err := json.NewDecoder(body).Decode(&data); err != nil {
|
||||||
|
return errors.Wrap(err, "could not unmarshal body")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) {
|
func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) {
|
||||||
|
@ -145,33 +158,17 @@ func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.
|
||||||
return nil, errors.New("ptp client: must have torrentID")
|
return nil, errors.New("ptp client: must have torrentID")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r TorrentResponse
|
var response TorrentResponse
|
||||||
|
|
||||||
v := url.Values{}
|
params := url.Values{}
|
||||||
v.Add("torrentid", torrentID)
|
params.Add("torrentid", torrentID)
|
||||||
params := v.Encode()
|
|
||||||
|
|
||||||
reqUrl := fmt.Sprintf("%v?%v", c.url, params)
|
err := c.getJSON(ctx, params, &response)
|
||||||
|
|
||||||
resp, err := c.get(ctx, reqUrl)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "error requesting data")
|
return nil, errors.Wrap(err, "error requesting data")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
for _, torrent := range response.Torrents {
|
||||||
resp.Body.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
body, readErr := io.ReadAll(resp.Body)
|
|
||||||
if readErr != nil {
|
|
||||||
return nil, errors.Wrap(readErr, "could not read body")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = json.Unmarshal(body, &r); err != nil {
|
|
||||||
return nil, errors.Wrap(readErr, "could not unmarshal body")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, torrent := range r.Torrents {
|
|
||||||
if torrent.Id == torrentID {
|
if torrent.Id == torrentID {
|
||||||
return &domain.TorrentBasic{
|
return &domain.TorrentBasic{
|
||||||
Id: torrent.Id,
|
Id: torrent.Id,
|
||||||
|
@ -181,21 +178,59 @@ func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, errors.New("could not find torrent with id: %s", torrentID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetTorrents(ctx context.Context) (*TorrentListResponse, error) {
|
||||||
|
var response TorrentListResponse
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
|
||||||
|
err := c.getJSON(ctx, params, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error requesting data")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestAPI try api access against torrents page
|
// TestAPI try api access against torrents page
|
||||||
func (c *Client) TestAPI(ctx context.Context) (bool, error) {
|
func (c *Client) TestAPI(ctx context.Context) (bool, error) {
|
||||||
resp, err := c.get(ctx, c.url)
|
resp, err := c.GetTorrents(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.Wrap(err, "error requesting data")
|
return false, errors.Wrap(err, "test api error")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
if resp == nil {
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TorrentListResponse struct {
|
||||||
|
TotalResults string `json:"TotalResults"`
|
||||||
|
Movies []Movie `json:"Movies"`
|
||||||
|
Page string `json:"Page"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Movie struct {
|
||||||
|
GroupID string `json:"GroupId"`
|
||||||
|
Title string `json:"Title"`
|
||||||
|
Year string `json:"Year"`
|
||||||
|
Cover string `json:"Cover"`
|
||||||
|
Tags []string `json:"Tags"`
|
||||||
|
Directors []Director `json:"Directors,omitempty"`
|
||||||
|
ImdbID *string `json:"ImdbId,omitempty"`
|
||||||
|
LastUploadTime string `json:"LastUploadTime"`
|
||||||
|
MaxSize int64 `json:"MaxSize"`
|
||||||
|
TotalSnatched int64 `json:"TotalSnatched"`
|
||||||
|
TotalSeeders int64 `json:"TotalSeeders"`
|
||||||
|
TotalLeechers int64 `json:"TotalLeechers"`
|
||||||
|
Torrents []Torrent `json:"Torrents"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Director struct {
|
||||||
|
Name string `json:"Name"`
|
||||||
|
ID string `json:"Id"`
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ package ptp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
|
@ -131,7 +132,12 @@ func Test(t *testing.T) {
|
||||||
// read json response
|
// read json response
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write(nil)
|
json.NewEncoder(w).Encode(TorrentListResponse{
|
||||||
|
TotalResults: "10",
|
||||||
|
Movies: []Movie{},
|
||||||
|
Page: "1",
|
||||||
|
})
|
||||||
|
//w.Write(nil)
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
|
|
113
pkg/red/red.go
113
pkg/red/red.go
|
@ -4,10 +4,10 @@
|
||||||
package red
|
package red
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -135,6 +135,30 @@ type Torrent struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IndexResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Response struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Id int `json:"id"`
|
||||||
|
Authkey string `json:"authkey"`
|
||||||
|
Passkey string `json:"passkey"`
|
||||||
|
ApiVersion string `json:"api_version"`
|
||||||
|
Notifications struct {
|
||||||
|
Messages int `json:"messages"`
|
||||||
|
Notifications int `json:"notifications"`
|
||||||
|
NewAnnouncement bool `json:"newAnnouncement"`
|
||||||
|
NewBlog bool `json:"newBlog"`
|
||||||
|
} `json:"notifications"`
|
||||||
|
UserStats struct {
|
||||||
|
Uploaded int64 `json:"uploaded"`
|
||||||
|
Downloaded int64 `json:"downloaded"`
|
||||||
|
Ratio float64 `json:"ratio"`
|
||||||
|
RequiredRatio float64 `json:"requiredratio"`
|
||||||
|
Class string `json:"class"`
|
||||||
|
} `json:"userstats"`
|
||||||
|
} `json:"response"`
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
||||||
//ctx := context.Background()
|
//ctx := context.Background()
|
||||||
err := c.rateLimiter.Wait(req.Context()) // This is a blocking call. Honors the rate limit
|
err := c.rateLimiter.Wait(req.Context()) // This is a blocking call. Honors the rate limit
|
||||||
|
@ -148,14 +172,16 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) get(ctx context.Context, url string) (*http.Response, error) {
|
func (c *Client) getJSON(ctx context.Context, params url.Values, data any) error {
|
||||||
if c.APIKey == "" {
|
if c.APIKey == "" {
|
||||||
return nil, errors.New("RED client missing API key!")
|
return errors.New("RED client missing API key!")
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
|
reqUrl := fmt.Sprintf("%s?%s", c.url, params.Encode())
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, http.NoBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "could not build request")
|
return errors.Wrap(err, "could not build request")
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Add("Authorization", c.APIKey)
|
req.Header.Add("Authorization", c.APIKey)
|
||||||
|
@ -163,28 +189,29 @@ func (c *Client) get(ctx context.Context, url string) (*http.Response, error) {
|
||||||
|
|
||||||
res, err := c.Do(req)
|
res, err := c.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, errors.Wrap(err, "could not make request: %+v", req)
|
return errors.Wrap(err, "could not make request: %+v", req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body := bufio.NewReader(res.Body)
|
||||||
|
|
||||||
// return early if not OK
|
// return early if not OK
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
var r ErrorResponse
|
var errResponse ErrorResponse
|
||||||
|
|
||||||
body, readErr := io.ReadAll(res.Body)
|
if err := json.NewDecoder(body).Decode(&errResponse); err != nil {
|
||||||
if readErr != nil {
|
return errors.Wrap(err, "could not unmarshal body")
|
||||||
return res, errors.Wrap(readErr, "could not read body")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = json.Unmarshal(body, &r); err != nil {
|
return errors.New("status code: %d status: %s error: %s", res.StatusCode, errResponse.Status, errResponse.Error)
|
||||||
return res, errors.Wrap(readErr, "could not unmarshal body")
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Body.Close()
|
|
||||||
|
|
||||||
return res, errors.New("status code: %d status: %s error: %s", res.StatusCode, r.Status, r.Error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
if err := json.NewDecoder(body).Decode(&data); err != nil {
|
||||||
|
return errors.Wrap(err, "could not unmarshal body")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) {
|
func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) {
|
||||||
|
@ -192,50 +219,50 @@ func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.
|
||||||
return nil, errors.New("red client: must have torrentID")
|
return nil, errors.New("red client: must have torrentID")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r TorrentDetailsResponse
|
var response TorrentDetailsResponse
|
||||||
|
|
||||||
v := url.Values{}
|
params := url.Values{}
|
||||||
v.Add("id", torrentID)
|
params.Add("action", "torrent")
|
||||||
params := v.Encode()
|
params.Add("id", torrentID)
|
||||||
|
|
||||||
reqUrl := fmt.Sprintf("%s?action=torrent&%s", c.url, params)
|
err := c.getJSON(ctx, params, &response)
|
||||||
|
|
||||||
resp, err := c.get(ctx, reqUrl)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "could not get torrent by id: %v", torrentID)
|
return nil, errors.Wrap(err, "could not get torrent by id: %v", torrentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, readErr := io.ReadAll(resp.Body)
|
|
||||||
if readErr != nil {
|
|
||||||
return nil, errors.Wrap(readErr, "could not read body")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(body, &r); err != nil {
|
|
||||||
return nil, errors.Wrap(readErr, "could not unmarshal body")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &domain.TorrentBasic{
|
return &domain.TorrentBasic{
|
||||||
Id: strconv.Itoa(r.Response.Torrent.Id),
|
Id: strconv.Itoa(response.Response.Torrent.Id),
|
||||||
InfoHash: r.Response.Torrent.InfoHash,
|
InfoHash: response.Response.Torrent.InfoHash,
|
||||||
Size: strconv.Itoa(r.Response.Torrent.Size),
|
Size: strconv.Itoa(response.Response.Torrent.Size),
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestAPI try api access against torrents page
|
// TestAPI try api access against torrents page
|
||||||
func (c *Client) TestAPI(ctx context.Context) (bool, error) {
|
func (c *Client) TestAPI(ctx context.Context) (bool, error) {
|
||||||
resp, err := c.get(ctx, c.url+"?action=index")
|
resp, err := c.GetIndex(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.Wrap(err, "test api error")
|
return false, errors.Wrap(err, "test api error")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
if resp == nil {
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetIndex get API index
|
||||||
|
func (c *Client) GetIndex(ctx context.Context) (*IndexResponse, error) {
|
||||||
|
var response IndexResponse
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("action", "index")
|
||||||
|
|
||||||
|
err := c.getJSON(ctx, params, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "test api error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
|
@ -4,15 +4,16 @@
|
||||||
package sabnzbd
|
package sabnzbd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/autobrr/autobrr/pkg/errors"
|
||||||
"github.com/autobrr/autobrr/pkg/sharedhttp"
|
"github.com/autobrr/autobrr/pkg/sharedhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -98,16 +99,14 @@ func (c *Client) AddFromUrl(ctx context.Context, r AddNzbRequest) (*AddFileRespo
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
body := bufio.NewReader(res.Body)
|
||||||
if err != nil {
|
if _, err := body.Peek(1); err != nil && err != bufio.ErrBufferFull {
|
||||||
return nil, err
|
return nil, errors.Wrap(err, "could not read body")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Print(body)
|
|
||||||
|
|
||||||
var data AddFileResponse
|
var data AddFileResponse
|
||||||
if err := json.Unmarshal(body, &data); err != nil {
|
if err := json.NewDecoder(body).Decode(&data); err != nil {
|
||||||
return nil, err
|
return nil, errors.Wrap(err, "could not unmarshal body")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &data, nil
|
return &data, nil
|
||||||
|
@ -147,14 +146,14 @@ func (c *Client) Version(ctx context.Context) (*VersionResponse, error) {
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
body := bufio.NewReader(res.Body)
|
||||||
if err != nil {
|
if _, err := body.Peek(1); err != nil && err != bufio.ErrBufferFull {
|
||||||
return nil, err
|
return nil, errors.Wrap(err, "could not read body")
|
||||||
}
|
}
|
||||||
|
|
||||||
var data VersionResponse
|
var data VersionResponse
|
||||||
if err := json.Unmarshal(body, &data); err != nil {
|
if err := json.NewDecoder(body).Decode(&data); err != nil {
|
||||||
return nil, err
|
return nil, errors.Wrap(err, "could not unmarshal body")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &data, nil
|
return &data, nil
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package whisparr
|
package whisparr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
@ -92,7 +93,7 @@ func (c *client) Test(ctx context.Context) (*SystemStatusResponse, error) {
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
body, err := io.ReadAll(bufio.NewReader(res.Body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "could not read body")
|
return nil, errors.Wrap(err, "could not read body")
|
||||||
}
|
}
|
||||||
|
@ -120,7 +121,7 @@ func (c *client) Push(ctx context.Context, release Release) ([]string, error) {
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
body, err := io.ReadAll(bufio.NewReader(res.Body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "could not read body")
|
return nil, errors.Wrap(err, "could not read body")
|
||||||
}
|
}
|
||||||
|
|
25
web/build.go
25
web/build.go
|
@ -5,6 +5,7 @@
|
||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -107,7 +108,7 @@ func fsFile(w http.ResponseWriter, r *http.Request, file string, filesystem fs.F
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := io.ReadAll(f)
|
data, err := io.ReadAll(bufio.NewReader(f))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Failed to read the file", http.StatusInternalServerError)
|
http.Error(w, "Failed to read the file", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -164,27 +165,7 @@ func RegisterHandler(c *chi.Mux, version, baseUrl string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if not valid web route then try and serve files
|
// if not valid web route then try and serve files
|
||||||
f, err := DistDirFS.Open(file)
|
fsFile(w, r, file, DistDirFS)
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "File not found", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
stat, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "File not found", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := io.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Failed to read the file", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := bytes.NewReader(data)
|
|
||||||
http.ServeContent(w, r, file, stat.ModTime(), reader)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue