Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74791d6e62 |
24
README.md
24
README.md
@@ -3,7 +3,7 @@
|
|||||||
This is a Golang implementation go Torrent Blackhole with a **Real Debrid Proxy Support**.
|
This is a Golang implementation go Torrent Blackhole with a **Real Debrid Proxy Support**.
|
||||||
|
|
||||||
#### Uses
|
#### Uses
|
||||||
- Torrent Blackhole that supports the Arrs.
|
- Torrent Blackhole that supports the Arrs(Sonarr, Radarr, etc)
|
||||||
- Proxy support for the Arrs
|
- Proxy support for the Arrs
|
||||||
|
|
||||||
The proxy is useful in filtering out un-cached Real Debrid torrents
|
The proxy is useful in filtering out un-cached Real Debrid torrents
|
||||||
@@ -73,12 +73,30 @@ Download the binary from the releases page and run it with the config file.
|
|||||||
"port": "8181",
|
"port": "8181",
|
||||||
"debug": false,
|
"debug": false,
|
||||||
"username": "username",
|
"username": "username",
|
||||||
"password": "password"
|
"password": "password",
|
||||||
|
"cached_only": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Proxy
|
#### Config Notes
|
||||||
|
##### Debrid Config
|
||||||
|
- This config key is important as it's used for both Blackhole and Proxy
|
||||||
|
|
||||||
|
##### Arrs Config
|
||||||
|
- An empty array will disable Blackhole for the Arrs
|
||||||
|
- The `watch_folder` is the folder where the Blackhole will watch for torrents
|
||||||
|
- The `completed_folder` is the folder where the Blackhole will move the completed torrents
|
||||||
|
- The `token` is the API key for the Arr(This is optional, I think)
|
||||||
|
|
||||||
|
##### Proxy Config
|
||||||
|
- The `enabled` key is used to enable the proxy
|
||||||
|
- The `port` key is the port the proxy will listen on
|
||||||
|
- The `debug` key is used to enable debug logs
|
||||||
|
- The `username` and `password` keys are used for basic authentication
|
||||||
|
- The `cached_only` means only cached torrents will be returned
|
||||||
|
-
|
||||||
|
### Proxy
|
||||||
|
|
||||||
The proxy is useful in filtering out un-cached Real Debrid torrents.
|
The proxy is useful in filtering out un-cached Real Debrid torrents.
|
||||||
The proxy is a simple HTTP proxy that requires basic authentication. The proxy can be enabled by setting the `proxy.enabled` to `true` in the config file.
|
The proxy is a simple HTTP proxy that requires basic authentication. The proxy can be enabled by setting the `proxy.enabled` to `true` in the config file.
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ func StartArr(conf *debrid.Arr, db debrid.Service) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StartBlackhole(config *common.Config, deb debrid.Service) {
|
func StartBlackhole(config *common.Config, deb debrid.Service) {
|
||||||
|
log.Println("[*] Starting Blackhole")
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for _, conf := range config.Arrs {
|
for _, conf := range config.Arrs {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|||||||
25
cmd/main.go
25
cmd/main.go
@@ -3,16 +3,33 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"goBlack/common"
|
"goBlack/common"
|
||||||
"goBlack/debrid"
|
"goBlack/debrid"
|
||||||
"log"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Start(config *common.Config) {
|
func Start(config *common.Config) {
|
||||||
|
|
||||||
log.Print("[*] BlackHole running")
|
|
||||||
deb := debrid.NewDebrid(config.Debrid)
|
deb := debrid.NewDebrid(config.Debrid)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
if config.Proxy.Enabled {
|
if config.Proxy.Enabled {
|
||||||
go StartProxy(config, deb)
|
proxy := NewProxy(*config, deb)
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
proxy.Start()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
StartBlackhole(config, deb)
|
|
||||||
|
if len(config.Arrs) > 0 {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
StartBlackhole(config, deb)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait indefinitely
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
153
cmd/proxy.go
153
cmd/proxy.go
@@ -16,7 +16,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RSS struct {
|
type RSS struct {
|
||||||
@@ -70,31 +69,37 @@ type TorznabAttr struct {
|
|||||||
Value string `xml:"value,attr"`
|
Value string `xml:"value,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SafeItems struct {
|
type Proxy struct {
|
||||||
mu sync.Mutex
|
Port string `json:"port"`
|
||||||
Items []Item
|
Enabled bool `json:"enabled"`
|
||||||
|
Debug bool `json:"debug"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
CachedOnly bool `json:"cached_only"`
|
||||||
|
Debrid debrid.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SafeItems) Add(item Item) {
|
func NewProxy(config common.Config, deb debrid.Service) *Proxy {
|
||||||
s.mu.Lock()
|
cfg := config.Proxy
|
||||||
defer s.mu.Unlock()
|
port := cmp.Or(os.Getenv("PORT"), cfg.Port, "8181")
|
||||||
s.Items = append(s.Items, item)
|
return &Proxy{
|
||||||
|
Port: port,
|
||||||
|
Enabled: cfg.Enabled,
|
||||||
|
Debug: cfg.Debug,
|
||||||
|
Username: cfg.Username,
|
||||||
|
Password: cfg.Password,
|
||||||
|
CachedOnly: cfg.CachedOnly,
|
||||||
|
Debrid: deb,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SafeItems) Get() []Item {
|
func (p *Proxy) ProcessJSONResponse(resp *http.Response) *http.Response {
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
return s.Items
|
|
||||||
}
|
|
||||||
|
|
||||||
func ProcessJSONResponse(resp *http.Response, deb debrid.Service) *http.Response {
|
|
||||||
if resp == nil || resp.Body == nil {
|
if resp == nil || resp.Body == nil {
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error reading response body:", err)
|
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
err = resp.Body.Close()
|
err = resp.Body.Close()
|
||||||
@@ -102,8 +107,8 @@ func ProcessJSONResponse(resp *http.Response, deb debrid.Service) *http.Response
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var p fastjson.Parser
|
var par fastjson.Parser
|
||||||
v, err := p.ParseBytes(body)
|
v, err := par.ParseBytes(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If it's not JSON, return the original response
|
// If it's not JSON, return the original response
|
||||||
resp.Body = io.NopCloser(bytes.NewReader(body))
|
resp.Body = io.NopCloser(bytes.NewReader(body))
|
||||||
@@ -124,24 +129,33 @@ func ProcessJSONResponse(resp *http.Response, deb debrid.Service) *http.Response
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProcessResponse(resp *http.Response, deb debrid.Service) *http.Response {
|
func (p *Proxy) ProcessResponse(resp *http.Response) *http.Response {
|
||||||
if resp == nil || resp.Body == nil {
|
if resp == nil || resp.Body == nil {
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
contentType := resp.Header.Get("Content-Type")
|
contentType := resp.Header.Get("Content-Type")
|
||||||
switch contentType {
|
switch contentType {
|
||||||
case "application/json":
|
case "application/json":
|
||||||
return ProcessJSONResponse(resp, deb)
|
return resp // p.ProcessJSONResponse(resp)
|
||||||
case "application/xml":
|
case "application/xml":
|
||||||
return ProcessXMLResponse(resp, deb)
|
return p.ProcessXMLResponse(resp)
|
||||||
case "application/rss+xml":
|
case "application/rss+xml":
|
||||||
return ProcessXMLResponse(resp, deb)
|
return p.ProcessXMLResponse(resp)
|
||||||
default:
|
default:
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func XMLItemIsCached(item Item, deb debrid.Service) bool {
|
func getItemsHash(items []Item) map[string]string {
|
||||||
|
IdHashMap := make(map[string]string)
|
||||||
|
for _, item := range items {
|
||||||
|
hash := getItemHash(item)
|
||||||
|
IdHashMap[item.GUID] = hash
|
||||||
|
}
|
||||||
|
return IdHashMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func getItemHash(item Item) string {
|
||||||
magnetLink := ""
|
magnetLink := ""
|
||||||
infohash := ""
|
infohash := ""
|
||||||
|
|
||||||
@@ -160,40 +174,33 @@ func XMLItemIsCached(item Item, deb debrid.Service) bool {
|
|||||||
}
|
}
|
||||||
if magnetLink == "" && infohash == "" {
|
if magnetLink == "" && infohash == "" {
|
||||||
// We can't check the availability of the torrent without a magnet link or infohash
|
// We can't check the availability of the torrent without a magnet link or infohash
|
||||||
return false
|
return ""
|
||||||
}
|
}
|
||||||
var magnet *common.Magnet
|
var magnet *common.Magnet
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if infohash == "" {
|
if infohash == "" {
|
||||||
magnet, err = common.GetMagnetInfo(magnetLink)
|
magnet, err = common.GetMagnetInfo(magnetLink)
|
||||||
if err != nil {
|
if err != nil || magnet == nil || magnet.InfoHash == "" {
|
||||||
log.Println("Error getting magnet info:", err)
|
log.Println("Error getting magnet info:", err)
|
||||||
return false
|
return ""
|
||||||
}
|
|
||||||
} else {
|
|
||||||
magnet = &common.Magnet{
|
|
||||||
InfoHash: infohash,
|
|
||||||
Name: item.Title,
|
|
||||||
Link: magnetLink,
|
|
||||||
}
|
}
|
||||||
|
infohash = magnet.InfoHash
|
||||||
}
|
}
|
||||||
if magnet == nil {
|
return infohash
|
||||||
log.Println("Error getting magnet info")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return deb.IsAvailable(magnet)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProcessXMLResponse(resp *http.Response, deb debrid.Service) *http.Response {
|
func (p *Proxy) ProcessXMLResponse(resp *http.Response) *http.Response {
|
||||||
if resp == nil || resp.Body == nil {
|
if resp == nil || resp.Body == nil {
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error reading response body:", err)
|
if p.Debug {
|
||||||
|
log.Println("Error reading response body:", err)
|
||||||
|
}
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
err = resp.Body.Close()
|
err = resp.Body.Close()
|
||||||
@@ -204,31 +211,51 @@ func ProcessXMLResponse(resp *http.Response, deb debrid.Service) *http.Response
|
|||||||
var rss RSS
|
var rss RSS
|
||||||
err = xml.Unmarshal(body, &rss)
|
err = xml.Unmarshal(body, &rss)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error unmarshalling XML: %v", err)
|
if p.Debug {
|
||||||
|
log.Printf("Error unmarshalling XML: %v", err)
|
||||||
|
}
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
newItems := &SafeItems{}
|
newItems := make([]Item, 0)
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
// Step 4: Extract infohash or magnet URI, manipulate data
|
// Step 4: Extract infohash or magnet URI, manipulate data
|
||||||
for _, item := range rss.Channel.Items {
|
IdsHashMap := getItemsHash(rss.Channel.Items)
|
||||||
wg.Add(1)
|
hashes := make([]string, 0)
|
||||||
go func(item Item) {
|
for _, hash := range IdsHashMap {
|
||||||
defer wg.Done()
|
if hash != "" {
|
||||||
if XMLItemIsCached(item, deb) {
|
hashes = append(hashes, hash)
|
||||||
newItems.Add(item)
|
}
|
||||||
}
|
|
||||||
}(item)
|
|
||||||
}
|
}
|
||||||
wg.Wait()
|
if len(hashes) == 0 {
|
||||||
items := newItems.Get()
|
// No infohashes or magnet links found, should we return the original response?
|
||||||
log.Printf("Report: %d/%d items are cached", len(items), len(rss.Channel.Items))
|
return resp
|
||||||
rss.Channel.Items = items
|
}
|
||||||
|
availableHashes := p.Debrid.IsAvailable(hashes)
|
||||||
|
for _, item := range rss.Channel.Items {
|
||||||
|
hash := IdsHashMap[item.GUID]
|
||||||
|
if hash == "" {
|
||||||
|
// newItems = append(newItems, item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
isCached, exists := availableHashes[hash]
|
||||||
|
if !exists {
|
||||||
|
// newItems = append(newItems, item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !isCached {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newItems = append(newItems, item)
|
||||||
|
}
|
||||||
|
log.Printf("Report: %d/%d items are cached", len(newItems), len(rss.Channel.Items))
|
||||||
|
rss.Channel.Items = newItems
|
||||||
|
|
||||||
// rss.Channel.Items = newItems
|
// rss.Channel.Items = newItems
|
||||||
modifiedBody, err := xml.MarshalIndent(rss, "", " ")
|
modifiedBody, err := xml.MarshalIndent(rss, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error marshalling XML: %v", err)
|
if p.Debug {
|
||||||
|
log.Printf("Error marshalling XML: %v", err)
|
||||||
|
}
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
modifiedBody = append([]byte(xml.Header), modifiedBody...)
|
modifiedBody = append([]byte(xml.Header), modifiedBody...)
|
||||||
@@ -247,9 +274,8 @@ func UrlMatches(re *regexp.Regexp) goproxy.ReqConditionFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartProxy(config *common.Config, deb debrid.Service) {
|
func (p *Proxy) Start() {
|
||||||
username, password := config.Proxy.Username, config.Proxy.Password
|
username, password := p.Username, p.Password
|
||||||
cfg := config.Proxy
|
|
||||||
proxy := goproxy.NewProxyHttpServer()
|
proxy := goproxy.NewProxyHttpServer()
|
||||||
if username != "" || password != "" {
|
if username != "" || password != "" {
|
||||||
// Set up basic auth for proxy
|
// Set up basic auth for proxy
|
||||||
@@ -261,12 +287,11 @@ func StartProxy(config *common.Config, deb debrid.Service) {
|
|||||||
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.443$"))).HandleConnect(goproxy.AlwaysMitm)
|
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.443$"))).HandleConnect(goproxy.AlwaysMitm)
|
||||||
proxy.OnResponse(UrlMatches(regexp.MustCompile("^.*/api\\?t=(search|tvsearch|movie)(&.*)?$"))).DoFunc(
|
proxy.OnResponse(UrlMatches(regexp.MustCompile("^.*/api\\?t=(search|tvsearch|movie)(&.*)?$"))).DoFunc(
|
||||||
func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
|
func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
|
||||||
return ProcessResponse(resp, deb)
|
return p.ProcessResponse(resp)
|
||||||
})
|
})
|
||||||
|
|
||||||
port := cmp.Or(os.Getenv("PORT"), cfg.Port, "8181")
|
proxy.Verbose = p.Debug
|
||||||
proxy.Verbose = cfg.Debug
|
portFmt := fmt.Sprintf(":%s", p.Port)
|
||||||
port = fmt.Sprintf(":%s", port)
|
log.Printf("[*] Starting proxy server on %s\n", portFmt)
|
||||||
log.Printf("Starting proxy server on %s\n", port)
|
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s", portFmt), proxy))
|
||||||
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s", port), proxy))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ type Config struct {
|
|||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
} `json:"arrs"`
|
} `json:"arrs"`
|
||||||
Proxy struct {
|
Proxy struct {
|
||||||
Port string `json:"port"`
|
Port string `json:"port"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
Debug bool `json:"debug"`
|
Debug bool `json:"debug"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
CachedOnly bool `json:"cached_only"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type Service interface {
|
|||||||
CheckStatus(torrent *Torrent) (*Torrent, error)
|
CheckStatus(torrent *Torrent) (*Torrent, error)
|
||||||
DownloadLink(torrent *Torrent) error
|
DownloadLink(torrent *Torrent) error
|
||||||
Process(arr *Arr, magnet string) (*Torrent, error)
|
Process(arr *Arr, magnet string) (*Torrent, error)
|
||||||
IsAvailable(magnet *common.Magnet) bool
|
IsAvailable(infohashes []string) map[string]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Debrid struct {
|
type Debrid struct {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (r *RealDebrid) Process(arr *Arr, magnet string) (*Torrent, error) {
|
|||||||
}
|
}
|
||||||
log.Printf("Torrent Name: %s", torrent.Name)
|
log.Printf("Torrent Name: %s", torrent.Name)
|
||||||
if !r.DownloadUncached {
|
if !r.DownloadUncached {
|
||||||
if !r.IsAvailable(torrent.Magnet) {
|
if !r.IsAvailable([]string{torrent.InfoHash})[torrent.InfoHash] {
|
||||||
return torrent, fmt.Errorf("torrent is not cached")
|
return torrent, fmt.Errorf("torrent is not cached")
|
||||||
}
|
}
|
||||||
log.Printf("Torrent: %s is cached", torrent.Name)
|
log.Printf("Torrent: %s is cached", torrent.Name)
|
||||||
@@ -41,22 +41,31 @@ func (r *RealDebrid) Process(arr *Arr, magnet string) (*Torrent, error) {
|
|||||||
return r.CheckStatus(torrent)
|
return r.CheckStatus(torrent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RealDebrid) IsAvailable(magnet *common.Magnet) bool {
|
func (r *RealDebrid) IsAvailable(infohashes []string) map[string]bool {
|
||||||
url := fmt.Sprintf("%s/torrents/instantAvailability/%s", r.Host, magnet.InfoHash)
|
hashes := strings.Join(infohashes, "/")
|
||||||
|
result := make(map[string]bool)
|
||||||
|
url := fmt.Sprintf("%s/torrents/instantAvailability/%s", r.Host, hashes)
|
||||||
resp, err := r.client.MakeRequest(http.MethodGet, url, nil)
|
resp, err := r.client.MakeRequest(http.MethodGet, url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
log.Println(url)
|
||||||
|
log.Println("Error checking availability:", err)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
var data structs.RealDebridAvailabilityResponse
|
var data structs.RealDebridAvailabilityResponse
|
||||||
err = json.Unmarshal(resp, &data)
|
err = json.Unmarshal(resp, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
log.Println("Error marshalling availability:", err)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
hosters, exists := data[strings.ToLower(magnet.InfoHash)]
|
for _, h := range infohashes {
|
||||||
if !exists || len(hosters) < 1 {
|
hosters, exists := data[strings.ToLower(h)]
|
||||||
return false
|
if !exists || len(hosters.Rd) < 1 {
|
||||||
|
result[h] = false
|
||||||
|
} else {
|
||||||
|
result[h] = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RealDebrid) SubmitMagnet(torrent *Torrent) (*Torrent, error) {
|
func (r *RealDebrid) SubmitMagnet(torrent *Torrent) (*Torrent, error) {
|
||||||
|
|||||||
@@ -1,10 +1,36 @@
|
|||||||
package structs
|
package structs
|
||||||
|
|
||||||
type RealDebridAvailabilityResponse map[string]Hosters
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
type Hosters map[string][]FileIDs
|
type RealDebridAvailabilityResponse map[string]Hoster
|
||||||
|
|
||||||
type FileIDs map[string]FileVariant
|
type Hoster struct {
|
||||||
|
Rd []map[string]FileVariant `json:"rd"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hoster) UnmarshalJSON(data []byte) error {
|
||||||
|
// Attempt to unmarshal into the expected structure (an object with an "rd" key)
|
||||||
|
type Alias Hoster
|
||||||
|
var obj Alias
|
||||||
|
if err := json.Unmarshal(data, &obj); err == nil {
|
||||||
|
*h = Hoster(obj)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If unmarshalling into an object fails, check if it's an empty array
|
||||||
|
var arr []interface{}
|
||||||
|
if err := json.Unmarshal(data, &arr); err == nil && len(arr) == 0 {
|
||||||
|
// It's an empty array; initialize with no entries
|
||||||
|
*h = Hoster{Rd: nil}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If both attempts fail, return an error
|
||||||
|
return fmt.Errorf("hoster: cannot unmarshal JSON data: %s", string(data))
|
||||||
|
}
|
||||||
|
|
||||||
type FileVariant struct {
|
type FileVariant struct {
|
||||||
Filename string `json:"filename"`
|
Filename string `json:"filename"`
|
||||||
@@ -39,3 +65,5 @@ type RealDebridTorrentInfo struct {
|
|||||||
Speed int `json:"speed,omitempty"`
|
Speed int `json:"speed,omitempty"`
|
||||||
Seeders int `json:"seeders,omitempty"`
|
Seeders int `json:"seeders,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5e6e2e77fd3921a7903a41336c844cc409bf8788/14527C07BDFDDFC642963238BB6E7507B9742947/66A1CD1A5C7F4014877A51AC2620E857E3BB4D16
|
||||||
|
|||||||
Reference in New Issue
Block a user