parent
9142be3688
commit
2685228c40
9
go.mod
9
go.mod
|
@ -12,11 +12,13 @@ require (
|
|||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
golang.org/x/net v0.15.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/gabriel-vasile/mimetype v1.4.3
|
||||
github.com/go-chi/chi v1.5.5
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/go-chi/render v1.0.3
|
||||
|
@ -27,8 +29,9 @@ require (
|
|||
github.com/nats-io/nats-server/v2 v2.10.2 // indirect
|
||||
github.com/nats-io/nkeys v0.4.5 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
golang.org/x/crypto v0.13.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
github.com/samber/lo v1.38.1
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
)
|
||||
|
|
15
go.sum
15
go.sum
|
@ -4,6 +4,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE=
|
||||
github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw=
|
||||
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||
|
@ -49,6 +51,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
|||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
|
@ -62,8 +66,11 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
|||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
@ -71,8 +78,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -83,8 +91,9 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
// CheckSuccess wraps a resty request and if the status code is not 2XX will make the error non-nil
|
||||
func CheckSuccess(resp *resty.Response, err error) (*resty.Response, error) {
|
||||
if err == nil && !resp.IsSuccess() {
|
||||
err = fmt.Errorf("%d status received: %s", resp.StatusCode(), string(resp.Body()))
|
||||
}
|
||||
return resp, err
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package gokapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"code.jhot.me/jhot/hats/internal/util"
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type GokapiClient struct {
|
||||
host string
|
||||
restClient *resty.Client
|
||||
}
|
||||
|
||||
func New(host string, token string) *GokapiClient {
|
||||
return &GokapiClient{
|
||||
host: host,
|
||||
restClient: resty.New().
|
||||
SetHeader("apikey", token).
|
||||
SetBaseURL(fmt.Sprintf("%s/api", host)),
|
||||
}
|
||||
}
|
||||
|
||||
func NewFromEnv() *GokapiClient {
|
||||
return New(os.Getenv("GOKAPI_HOST"), os.Getenv("GOKAPI_TOKEN"))
|
||||
}
|
||||
|
||||
type GokapiFile struct {
|
||||
ID string `json:"Id"`
|
||||
Name string `json:"Name"`
|
||||
Size string `json:"Size"`
|
||||
HotlinkId string `json:"HotlinkId"`
|
||||
ContentType string `json:"ContentType"`
|
||||
ExpireAt int64 `json:"ExpireAt"`
|
||||
SizeBytes int64 `json:"SizeBytes"`
|
||||
ExpireAtString string `json:"ExpireAtString"`
|
||||
DownloadsRemaining int `json:"DownloadsRemaining"`
|
||||
DownloadCount int `json:"DownloadCount"`
|
||||
UnlimitedDownloads bool `json:"UnlimitedDownloads"`
|
||||
UnlimitedTime bool `json:"UnlimitedTime"`
|
||||
RequiresClientSideDecryption bool `json:"RequiresClientSideDecryption"`
|
||||
IsEncrypted bool `json:"IsEncrypted"`
|
||||
IsPasswordProtected bool `json:"IsPasswordProtected"`
|
||||
IsSavedOnLocalStorage bool `json:"IsSavedOnLocalStorage"`
|
||||
}
|
||||
|
||||
func (c *GokapiClient) ListFiles() ([]GokapiFile, error) {
|
||||
var data []GokapiFile
|
||||
_, err := util.CheckSuccess(c.restClient.R().SetHeader("Accept", "application/json").SetResult(&data).Get("files/list"))
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
type UploadOptions struct {
|
||||
AllowedDownloads int
|
||||
ExpiryDays int
|
||||
Password string
|
||||
FilenameOverride string
|
||||
}
|
||||
|
||||
func (c *GokapiClient) UploadFile(filePath string, opts *UploadOptions) (GokapiFile, error) {
|
||||
var fileName string
|
||||
if opts.FilenameOverride != "" {
|
||||
fileName = opts.FilenameOverride
|
||||
} else {
|
||||
fileName = filepath.Base(filePath)
|
||||
}
|
||||
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return GokapiFile{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
mimeType, _ := mimetype.DetectFile(filePath)
|
||||
|
||||
req := c.restClient.R().
|
||||
SetMultipartField("file", fileName, mimeType.String(), f).
|
||||
SetMultipartFormData(map[string]string{
|
||||
"allowedDownloads": fmt.Sprintf("%d", opts.AllowedDownloads),
|
||||
"expiryDays": fmt.Sprintf("%d", opts.ExpiryDays),
|
||||
"password": opts.Password,
|
||||
})
|
||||
|
||||
var data struct {
|
||||
Result string `json:"Result"`
|
||||
FileInfo GokapiFile `json:"FileInfo"`
|
||||
}
|
||||
|
||||
_, err = util.CheckSuccess(req.SetResult(&data).Post("files/add"))
|
||||
if err != nil {
|
||||
return GokapiFile{}, err
|
||||
}
|
||||
|
||||
return data.FileInfo, nil
|
||||
}
|
||||
|
||||
func (c *GokapiClient) GetDownloadUrl(f GokapiFile) string {
|
||||
return fmt.Sprintf("%s/downloadFile?id=%s", c.host, f.ID)
|
||||
}
|
|
@ -58,6 +58,7 @@ var Services = struct {
|
|||
SetHvacMode string
|
||||
SetFanMode string
|
||||
SetTemperature string
|
||||
SetValue string
|
||||
Start string
|
||||
Change string
|
||||
Cancel string
|
||||
|
@ -74,6 +75,7 @@ var Services = struct {
|
|||
SetHvacMode: "set_hvac_mode",
|
||||
SetFanMode: "set_fan_mode",
|
||||
SetTemperature: "set_temperature",
|
||||
SetValue: "set_value",
|
||||
Start: "start",
|
||||
Change: "change",
|
||||
Cancel: "cancel",
|
||||
|
@ -89,6 +91,7 @@ var ExtraProps = struct {
|
|||
TargetTempHigh string
|
||||
TargetTempLow string
|
||||
Duration string
|
||||
Value string
|
||||
}{
|
||||
Transition: "transition",
|
||||
Brightness: "brightness",
|
||||
|
@ -98,6 +101,7 @@ var ExtraProps = struct {
|
|||
TargetTempHigh: "target_temp_high",
|
||||
TargetTempLow: "target_temp_low",
|
||||
Duration: "duration",
|
||||
Value: "value",
|
||||
}
|
||||
|
||||
type ResultContext struct {
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
package nws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.jhot.me/jhot/hats/internal/util"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
const (
|
||||
NWS_BASE_URL = "https://api.weather.gov"
|
||||
)
|
||||
|
||||
type NwsClient struct {
|
||||
restClient *resty.Client
|
||||
}
|
||||
|
||||
func New() *NwsClient {
|
||||
return &NwsClient{
|
||||
restClient: resty.New().
|
||||
SetBaseURL(NWS_BASE_URL).
|
||||
SetHeader("Accept", "application/geo+json").
|
||||
SetHeader("User-Agent", util.GetEnvWithDefault("NWS_USER_AGENT", "(HATS-client, hats@jhot.me)")),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *NwsClient) CoordinatesToPoint(lat float64, lon float64) (Point, error) {
|
||||
var p Point
|
||||
_, err := c.restClient.R().SetResult(&p).Get(fmt.Sprintf("points/%f,%f", lat, lon))
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (c *NwsClient) GetHourlyForecast(p Point) (HourlyProps, error) {
|
||||
var data struct {
|
||||
Properties HourlyProps `json:"properties"`
|
||||
}
|
||||
_, err := util.CheckSuccess(c.restClient.R().SetResult(&data).
|
||||
Get(removeBase(p.Properties.ForecastHourly)))
|
||||
if err != nil {
|
||||
return HourlyProps{}, nil
|
||||
}
|
||||
|
||||
return data.Properties, nil
|
||||
}
|
||||
|
||||
func (p HourlyProps) GetHighLow(hours int) (high int, low int) {
|
||||
if hours > len(p.Periods) {
|
||||
hours = len(p.Periods)
|
||||
}
|
||||
|
||||
temps := lo.Map(p.Periods[0:hours-1], func(p HourlyPeriod, _ int) int {
|
||||
return p.Temperature
|
||||
})
|
||||
|
||||
return lo.Max(temps), lo.Min(temps)
|
||||
}
|
||||
|
||||
func (c *NwsClient) GetStations(p Point) ([]string, error) {
|
||||
var data struct {
|
||||
ObservationStations []string `json:"observationStations"`
|
||||
}
|
||||
_, err := util.CheckSuccess(c.restClient.R().SetResult(&data).
|
||||
Get(removeBase(p.Properties.ObservationStations)))
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
return data.ObservationStations, nil
|
||||
}
|
||||
|
||||
func (c *NwsClient) GetLatestObservations(p Point) (Observations, error) {
|
||||
stations, err := c.GetStations(p)
|
||||
if err != nil {
|
||||
return Observations{}, fmt.Errorf("%w: error getting stations", err)
|
||||
}
|
||||
|
||||
var data struct {
|
||||
Properties Observations `json:"properties"`
|
||||
}
|
||||
_, err = util.CheckSuccess(c.restClient.R().SetResult(&data).
|
||||
Get(removeBase(stations[0] + "/observations/latest")))
|
||||
if err != nil {
|
||||
return Observations{}, err
|
||||
}
|
||||
|
||||
return data.Properties, nil
|
||||
}
|
||||
|
||||
func removeBase(longUrl string) string {
|
||||
return strings.TrimPrefix(longUrl, NWS_BASE_URL+"/")
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package nws
|
||||
|
||||
import "time"
|
||||
|
||||
type UnitValue[T any] struct {
|
||||
UnitCode string `json:"unitCode"`
|
||||
Value T `json:"value"`
|
||||
}
|
||||
|
||||
type PointProps struct {
|
||||
GridId string `json:"gridId"`
|
||||
GridX int `json:"gridX"`
|
||||
GridY int `json:"gridY"`
|
||||
Forecast string `json:"forecast"`
|
||||
ForecastHourly string `json:"forecastHourly"`
|
||||
ForecastGridData string `json:"forecastGridData"`
|
||||
ForecastZone string `json:"forecastZone"`
|
||||
TimeZone string `json:"timeZone"`
|
||||
RadarStation string `json:"radarStation"`
|
||||
ObservationStations string `json:"observationStations"`
|
||||
}
|
||||
|
||||
type Point struct {
|
||||
ID string `json:"id"`
|
||||
Properties PointProps `json:"properties"`
|
||||
}
|
||||
|
||||
type HourlyPeriod struct {
|
||||
Number int `json:"number"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
Endime time.Time `json:"endTime"`
|
||||
IsDayTime bool `json:"isDayTime"`
|
||||
Temperature int `json:"temperature"`
|
||||
ProbabilityOfPrecipitation UnitValue[int] `json:"probabilityOfPrecipitation"`
|
||||
DewPoint UnitValue[float64] `json:"dewpoint"`
|
||||
ShortForecast string `json:"shortForecast"`
|
||||
}
|
||||
|
||||
type HourlyProps struct {
|
||||
Updated time.Time `json:"updated"`
|
||||
Units string `json:"units"`
|
||||
Periods []HourlyPeriod `json:"periods"`
|
||||
}
|
||||
|
||||
type Observations struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Temperature UnitValue[float64] `json:"temperature"`
|
||||
DewPoint UnitValue[float64] `json:"dewpoint"`
|
||||
WindDirection UnitValue[int] `json:"windDirection"`
|
||||
WindSpeed UnitValue[float64] `json:"windSpeed"`
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"code.jhot.me/jhot/hats/internal/util"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type QbittorrentClient struct {
|
||||
restClient *resty.Client
|
||||
}
|
||||
|
||||
func New(host string) *QbittorrentClient {
|
||||
return &QbittorrentClient{
|
||||
restClient: resty.New().SetBaseURL(fmt.Sprintf("%s/api/v2", host)),
|
||||
}
|
||||
}
|
||||
|
||||
func NewFromEnv() (*QbittorrentClient, error) {
|
||||
c := New(os.Getenv("QBITTORRENT_HOST"))
|
||||
err := c.Login(os.Getenv("QBITTORRENT_USER"), os.Getenv("QBITTORRENT_PASS"))
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (c *QbittorrentClient) Login(user string, pass string) error {
|
||||
resp, err := util.CheckSuccess(c.restClient.R().SetFormData(map[string]string{
|
||||
"username": user,
|
||||
"password": pass,
|
||||
}).Post("auth/login"))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authCookie := resp.Header().Get("set-cookie")
|
||||
if authCookie == "" {
|
||||
return errors.New("auth cookie not found")
|
||||
}
|
||||
|
||||
first := strings.Split(authCookie, ";")[0]
|
||||
parts := strings.Split(first, "=")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("auth cookie not in expected format: %s", authCookie)
|
||||
}
|
||||
|
||||
c.restClient.SetCookie(&http.Cookie{
|
||||
Name: parts[0],
|
||||
Value: parts[1],
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *QbittorrentClient) GetVersion() (string, error) {
|
||||
resp, err := util.CheckSuccess(c.restClient.R().Get("app/version"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(resp.Body()), nil
|
||||
}
|
||||
|
||||
func (c *QbittorrentClient) GetTransferInfo() (GlobalTransferInfo, error) {
|
||||
var data GlobalTransferInfo
|
||||
_, err := util.CheckSuccess(c.restClient.R().SetResult(&data).Get("transfer/info"))
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (c *QbittorrentClient) GetAltSpeedLimitState() (bool, error) {
|
||||
resp, err := util.CheckSuccess(c.restClient.R().Get("transfer/speedLimitsMode"))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return string(resp.Body()) == "1", nil
|
||||
}
|
||||
|
||||
func (c *QbittorrentClient) ToggleAltSpeedLimitState() error {
|
||||
_, err := util.CheckSuccess(c.restClient.R().Post("transfer/toggleSpeedLimitsMode"))
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package qbittorrent
|
||||
|
||||
type GlobalTransferInfo struct {
|
||||
DownloadSpeed int `json:"dl_info_speed"`
|
||||
DownloadedData int `json:"dl_info_data"`
|
||||
UploadSpeed int `json:"up_info_speed"`
|
||||
UploadedData int `json:"up_info_data"`
|
||||
DhtNodes int `json:"dht_nodes"`
|
||||
Status string `json:"connection_status"`
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package syncthing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"code.jhot.me/jhot/hats/internal/util"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type SyncthingClient struct {
|
||||
restClient *resty.Client
|
||||
}
|
||||
|
||||
func New(host string, token string) *SyncthingClient {
|
||||
return &SyncthingClient{
|
||||
restClient: resty.New().
|
||||
SetBaseURL(fmt.Sprintf("%s/rest", host)).
|
||||
SetHeader("X-API-Key", token),
|
||||
}
|
||||
}
|
||||
|
||||
func NewFromEnv() *SyncthingClient {
|
||||
return New(os.Getenv("SYNCTHING_HOST"), os.Getenv("SYNCTHING_TOKEN"))
|
||||
}
|
||||
|
||||
func (c *SyncthingClient) GetVersion() (VersionInfo, error) {
|
||||
var data VersionInfo
|
||||
_, err := util.CheckSuccess(c.restClient.R().SetResult(&data).Get("system/version"))
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (c *SyncthingClient) GetDeviceStats() (map[string]DeviceStatistics, error) {
|
||||
var data map[string]DeviceStatistics
|
||||
_, err := util.CheckSuccess(c.restClient.R().SetResult(&data).Get("stats/device"))
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (c *SyncthingClient) Pause(id string) error {
|
||||
req := c.restClient.R()
|
||||
|
||||
if id != "" {
|
||||
req.SetHeader("device", id)
|
||||
}
|
||||
|
||||
_, err := util.CheckSuccess(req.Post("system/pause"))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *SyncthingClient) Resume(id string) error {
|
||||
req := c.restClient.R()
|
||||
|
||||
if id != "" {
|
||||
req.SetHeader("device", id)
|
||||
}
|
||||
|
||||
_, err := util.CheckSuccess(req.Post("system/resume"))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *SyncthingClient) GetFolderConfig(id string) ([]FolderConfig, error) {
|
||||
url := "config/folders"
|
||||
|
||||
if id != "" {
|
||||
|
||||
url = fmt.Sprintf("%s/%s", url, id)
|
||||
var data FolderConfig
|
||||
_, err := util.CheckSuccess(c.restClient.R().SetResult(&data).Get(url))
|
||||
return []FolderConfig{data}, err
|
||||
|
||||
} else {
|
||||
|
||||
var data []FolderConfig
|
||||
_, err := util.CheckSuccess(c.restClient.R().SetResult(&data).Get(url))
|
||||
return data, err
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SyncthingClient) SetFolderConfig(id string, param string, value any) error {
|
||||
_, err := util.CheckSuccess(c.restClient.R().SetBody(map[string]any{param: value}).Patch(fmt.Sprintf("config/folders/%s", id)))
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package syncthing
|
||||
|
||||
type VersionInfo struct {
|
||||
Arch string `json:"arch"`
|
||||
LongVersion string `json:"longVersion"`
|
||||
OS string `json:"os"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type DeviceStatistics struct {
|
||||
LastSeen string `json:"lastSeen"`
|
||||
LastConnectionDuration int `json:"lastConnectionDurationS"`
|
||||
}
|
||||
|
||||
type FolderConfig struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label,omitempty"`
|
||||
FilesystemType string `json:"filesystemType,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
RescanIntervalS int `json:"rescanIntervalS,omitempty"`
|
||||
FsWatcherEnabled bool `json:"fsWatcherEnabled,omitempty"`
|
||||
FsWatcherDelayS int `json:"fsWatcherDelayS,omitempty"`
|
||||
IgnorePerms bool `json:"ignorePerms,omitempty"`
|
||||
AutoNormalize bool `json:"autoNormalize,omitempty"`
|
||||
Copiers int `json:"copiers,omitempty"`
|
||||
PullerMaxPendingKiB int `json:"pullerMaxPendingKiB,omitempty"`
|
||||
Hashers int `json:"hashers,omitempty"`
|
||||
Order string `json:"order,omitempty"`
|
||||
IgnoreDelete bool `json:"ignoreDelete,omitempty"`
|
||||
ScanProgressIntervalS int `json:"scanProgressIntervalS,omitempty"`
|
||||
PullerPauseS int `json:"pullerPauseS,omitempty"`
|
||||
MaxConflicts int `json:"maxConflicts,omitempty"`
|
||||
DisableSparseFiles bool `json:"disableSparseFiles,omitempty"`
|
||||
DisableTempIndexes bool `json:"disableTempIndexes,omitempty"`
|
||||
Paused bool `json:"paused,omitempty"`
|
||||
WeakHashThresholdPct int `json:"weakHashThresholdPct,omitempty"`
|
||||
MarkerName string `json:"markerName,omitempty"`
|
||||
CopyOwnershipFromParent bool `json:"copyOwnershipFromParent,omitempty"`
|
||||
ModTimeWindowS int `json:"modTimeWindowS,omitempty"`
|
||||
MaxConcurrentWrites int `json:"maxConcurrentWrites,omitempty"`
|
||||
DisableFsync bool `json:"disableFsync,omitempty"`
|
||||
BlockPullOrder string `json:"blockPullOrder,omitempty"`
|
||||
CopyRangeMethod string `json:"copyRangeMethod,omitempty"`
|
||||
CaseSensitiveFS bool `json:"caseSensitiveFS,omitempty"`
|
||||
JunctionsAsDirs bool `json:"junctionsAsDirs,omitempty"`
|
||||
SyncOwnership bool `json:"syncOwnership,omitempty"`
|
||||
SendOwnership bool `json:"sendOwnership,omitempty"`
|
||||
SyncXattrs bool `json:"syncXattrs,omitempty"`
|
||||
SendXattrs bool `json:"sendXattrs,omitempty"`
|
||||
}
|
Loading…
Reference in New Issue