Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6c6144601 | ||
|
|
ff74e279d9 | ||
|
|
ba147ac56c |
@@ -60,4 +60,11 @@
|
|||||||
|
|
||||||
- Delete uncached items from RD
|
- Delete uncached items from RD
|
||||||
- Fail if the torrent is not cached(optional)
|
- Fail if the torrent is not cached(optional)
|
||||||
|
- Fix cache not being updated
|
||||||
|
|
||||||
|
#### 0.2.4
|
||||||
|
|
||||||
|
- Add file download support(Sequential Download)
|
||||||
|
- Fix http handler error
|
||||||
|
- Fix *arrs map failing concurrently
|
||||||
- Fix cache not being updated
|
- Fix cache not being updated
|
||||||
@@ -18,6 +18,7 @@ ADD . .
|
|||||||
RUN CGO_ENABLED=0 GOOS=$(echo $TARGETPLATFORM | cut -d '/' -f1) GOARCH=$(echo $TARGETPLATFORM | cut -d '/' -f2) go build -o /blackhole
|
RUN CGO_ENABLED=0 GOOS=$(echo $TARGETPLATFORM | cut -d '/' -f1) GOARCH=$(echo $TARGETPLATFORM | cut -d '/' -f2) go build -o /blackhole
|
||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
COPY --from=builder /blackhole /blackhole
|
COPY --from=builder /blackhole /blackhole
|
||||||
|
|
||||||
EXPOSE 8181
|
EXPOSE 8181
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ Setting Up Qbittorrent in Arr
|
|||||||
- Password: `sonarr_token` # Your arr token
|
- Password: `sonarr_token` # Your arr token
|
||||||
- Category: e.g `sonarr`, `radarr`
|
- Category: e.g `sonarr`, `radarr`
|
||||||
- Use SSL -> `No`
|
- Use SSL -> `No`
|
||||||
|
- Sequential Download -> `No`|`Yes` (If you want to download the torrents locally instead of symlink)
|
||||||
- Test
|
- Test
|
||||||
- Save
|
- Save
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
VIDEOMATCH = "(?i)(\\.)(YUV|WMV|WEBM|VOB|VIV|SVI|ROQ|RMVB|RM|OGV|OGG|NSV|MXF|MPG|MPEG|M2V|MP2|MPE|MPV|MP4|M4P|M4V|MOV|QT|MNG|MKV|FLV|DRC|AVI|ASF|AMV|MKA|F4V|3GP|3G2|DIVX|X264|X265)$"
|
VIDEOMATCH = "(?i)(\\.)(YUV|WMV|WEBM|VOB|VIV|SVI|ROQ|RMVB|RM|OGV|OGG|NSV|MXF|MPG|MPEG|M2V|MP2|MPE|MPV|MP4|M4P|M4V|MOV|QT|MNG|MKV|FLV|DRC|AVI|ASF|AMV|MKA|F4V|3GP|3G2|DIVX|X264|X265)$"
|
||||||
|
MUSICMATCH = "(?i)(\\.)(?:MP3|WAV|FLAC|AAC|OGG|WMA|AIFF|ALAC|M4A|APE|AC3|DTS|M4P|MID|MIDI|MKA|MP2|MPA|RA|VOC|WV|AMR)$"
|
||||||
SUBMATCH = "(?i)(\\.)(SRT|SUB|SBV|ASS|VTT|TTML|DFXP|STL|SCC|CAP|SMI|TTXT|TDS|USF|JSS|SSA|PSB|RT|LRC|SSB)$"
|
SUBMATCH = "(?i)(\\.)(SRT|SUB|SBV|ASS|VTT|TTML|DFXP|STL|SCC|CAP|SMI|TTXT|TDS|USF|JSS|SSA|PSB|RT|LRC|SSB)$"
|
||||||
SAMPLEMATCH = `(?i)(^|[\\/]|[._-])(sample|trailer|thumb)s?([._-]|$)`
|
SAMPLEMATCH = `(?i)(^|[\\/]|[._-])(sample|trailer|thumb)s?([._-]|$)`
|
||||||
)
|
)
|
||||||
@@ -36,7 +37,7 @@ func RemoveInvalidChars(value string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RemoveExtension(value string) string {
|
func RemoveExtension(value string) string {
|
||||||
re := regexp.MustCompile(VIDEOMATCH + "|" + SUBMATCH + "|" + SAMPLEMATCH)
|
re := regexp.MustCompile(VIDEOMATCH + "|" + SUBMATCH + "|" + SAMPLEMATCH + "|" + MUSICMATCH)
|
||||||
|
|
||||||
// Find the last index of the matched extension
|
// Find the last index of the matched extension
|
||||||
loc := re.FindStringIndex(value)
|
loc := re.FindStringIndex(value)
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ func NewLogger(prefix string, output *os.File) *log.Logger {
|
|||||||
func GetInfohashFromURL(url string) (string, error) {
|
func GetInfohashFromURL(url string) (string, error) {
|
||||||
// Download the torrent file
|
// Download the torrent file
|
||||||
var magnetLink string
|
var magnetLink string
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: 30 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
|
|||||||
5
go.mod
5
go.mod
@@ -4,10 +4,12 @@ go 1.22
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/anacrolix/torrent v1.55.0
|
github.com/anacrolix/torrent v1.55.0
|
||||||
|
github.com/cavaliergopher/grab/v3 v3.0.1
|
||||||
github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380
|
github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380
|
||||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2
|
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2
|
||||||
github.com/go-chi/chi/v5 v5.1.0
|
github.com/go-chi/chi/v5 v5.1.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/valyala/fasthttp v1.55.0
|
||||||
github.com/valyala/fastjson v1.6.4
|
github.com/valyala/fastjson v1.6.4
|
||||||
golang.org/x/time v0.6.0
|
golang.org/x/time v0.6.0
|
||||||
)
|
)
|
||||||
@@ -15,9 +17,12 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/anacrolix/missinggo v1.3.0 // indirect
|
github.com/anacrolix/missinggo v1.3.0 // indirect
|
||||||
github.com/anacrolix/missinggo/v2 v2.7.3 // indirect
|
github.com/anacrolix/missinggo/v2 v2.7.3 // indirect
|
||||||
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/huandu/xstrings v1.3.2 // indirect
|
github.com/huandu/xstrings v1.3.2 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.9 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
golang.org/x/net v0.27.0 // indirect
|
golang.org/x/net v0.27.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
10
go.sum
10
go.sum
@@ -35,6 +35,8 @@ github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pm
|
|||||||
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
|
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
|
||||||
github.com/anacrolix/torrent v1.55.0 h1:s9yh/YGdPmbN9dTa+0Inh2dLdrLQRvEAj1jdFW/Hdd8=
|
github.com/anacrolix/torrent v1.55.0 h1:s9yh/YGdPmbN9dTa+0Inh2dLdrLQRvEAj1jdFW/Hdd8=
|
||||||
github.com/anacrolix/torrent v1.55.0/go.mod h1:sBdZHBSZNj4de0m+EbYg7vvs/G/STubxu/GzzNbojsE=
|
github.com/anacrolix/torrent v1.55.0/go.mod h1:sBdZHBSZNj4de0m+EbYg7vvs/G/STubxu/GzzNbojsE=
|
||||||
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI=
|
github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
@@ -44,6 +46,8 @@ github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2w
|
|||||||
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
|
||||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
|
||||||
|
github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4=
|
||||||
|
github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -121,6 +125,8 @@ github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVY
|
|||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||||
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
@@ -187,6 +193,10 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
|||||||
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8=
|
||||||
|
github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM=
|
||||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||||
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
SubmitMagnet(torrent *Torrent) (*Torrent, error)
|
SubmitMagnet(torrent *Torrent) (*Torrent, error)
|
||||||
CheckStatus(torrent *Torrent) (*Torrent, error)
|
CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, error)
|
||||||
DownloadLink(torrent *Torrent) error
|
GetDownloadLinks(torrent *Torrent) error
|
||||||
DeleteTorrent(torrent *Torrent)
|
DeleteTorrent(torrent *Torrent)
|
||||||
IsAvailable(infohashes []string) map[string]bool
|
IsAvailable(infohashes []string) map[string]bool
|
||||||
GetMountPath() string
|
GetMountPath() string
|
||||||
@@ -120,7 +120,7 @@ func GetLocalCache(infohashes []string, cache *common.Cache) ([]string, map[stri
|
|||||||
return hashes, result
|
return hashes, result
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProcessQBitTorrent(d Service, magnet *common.Magnet, arr *Arr) (*Torrent, error) {
|
func ProcessQBitTorrent(d Service, magnet *common.Magnet, arr *Arr, isSymlink bool) (*Torrent, error) {
|
||||||
debridTorrent := &Torrent{
|
debridTorrent := &Torrent{
|
||||||
InfoHash: magnet.InfoHash,
|
InfoHash: magnet.InfoHash,
|
||||||
Magnet: magnet,
|
Magnet: magnet,
|
||||||
@@ -144,5 +144,5 @@ func ProcessQBitTorrent(d Service, magnet *common.Magnet, arr *Arr) (*Torrent, e
|
|||||||
logger.Printf("Error submitting magnet: %s", err)
|
logger.Printf("Error submitting magnet: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return d.CheckStatus(debridTorrent)
|
return d.CheckStatus(debridTorrent, isSymlink)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ func GetTorrentFiles(data structs.RealDebridTorrentInfo) []TorrentFile {
|
|||||||
files := make([]TorrentFile, 0)
|
files := make([]TorrentFile, 0)
|
||||||
for _, f := range data.Files {
|
for _, f := range data.Files {
|
||||||
name := filepath.Base(f.Path)
|
name := filepath.Base(f.Path)
|
||||||
if (!common.RegexMatch(common.VIDEOMATCH, name) && !common.RegexMatch(common.SUBMATCH, name)) || common.RegexMatch(common.SAMPLEMATCH, name) {
|
if (!common.RegexMatch(common.VIDEOMATCH, name) &&
|
||||||
|
!common.RegexMatch(common.SUBMATCH, name) &&
|
||||||
|
!common.RegexMatch(common.MUSICMATCH, name)) || common.RegexMatch(common.SAMPLEMATCH, name) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fileId := f.ID
|
fileId := f.ID
|
||||||
@@ -149,12 +151,13 @@ func (r *RealDebrid) GetTorrent(id string) (*Torrent, error) {
|
|||||||
torrent.Seeders = data.Seeders
|
torrent.Seeders = data.Seeders
|
||||||
torrent.Filename = data.Filename
|
torrent.Filename = data.Filename
|
||||||
torrent.OriginalFilename = data.OriginalFilename
|
torrent.OriginalFilename = data.OriginalFilename
|
||||||
|
torrent.Links = data.Links
|
||||||
files := GetTorrentFiles(data)
|
files := GetTorrentFiles(data)
|
||||||
torrent.Files = files
|
torrent.Files = files
|
||||||
return torrent, nil
|
return torrent, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RealDebrid) CheckStatus(torrent *Torrent) (*Torrent, error) {
|
func (r *RealDebrid) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, error) {
|
||||||
url := fmt.Sprintf("%s/torrents/info/%s", r.Host, torrent.Id)
|
url := fmt.Sprintf("%s/torrents/info/%s", r.Host, torrent.Id)
|
||||||
for {
|
for {
|
||||||
resp, err := r.client.MakeRequest(http.MethodGet, url, nil)
|
resp, err := r.client.MakeRequest(http.MethodGet, url, nil)
|
||||||
@@ -174,6 +177,8 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent) (*Torrent, error) {
|
|||||||
torrent.Progress = data.Progress
|
torrent.Progress = data.Progress
|
||||||
torrent.Speed = data.Speed
|
torrent.Speed = data.Speed
|
||||||
torrent.Seeders = data.Seeders
|
torrent.Seeders = data.Seeders
|
||||||
|
torrent.Links = data.Links
|
||||||
|
torrent.Status = status
|
||||||
if status == "error" || status == "dead" || status == "magnet_error" {
|
if status == "error" || status == "dead" || status == "magnet_error" {
|
||||||
return torrent, fmt.Errorf("torrent: %s has error", torrent.Name)
|
return torrent, fmt.Errorf("torrent: %s has error", torrent.Name)
|
||||||
} else if status == "waiting_files_selection" {
|
} else if status == "waiting_files_selection" {
|
||||||
@@ -195,11 +200,16 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent) (*Torrent, error) {
|
|||||||
return torrent, err
|
return torrent, err
|
||||||
}
|
}
|
||||||
} else if status == "downloaded" {
|
} else if status == "downloaded" {
|
||||||
log.Printf("Torrent: %s downloaded\n", torrent.Name)
|
files := GetTorrentFiles(data)
|
||||||
err = r.DownloadLink(torrent)
|
torrent.Files = files
|
||||||
if err != nil {
|
log.Printf("Torrent: %s downloaded to RD\n", torrent.Name)
|
||||||
return torrent, err
|
if !isSymlink {
|
||||||
|
err = r.GetDownloadLinks(torrent)
|
||||||
|
if err != nil {
|
||||||
|
return torrent, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
} else if status == "downloading" {
|
} else if status == "downloading" {
|
||||||
if !r.DownloadUncached {
|
if !r.DownloadUncached {
|
||||||
@@ -225,7 +235,32 @@ func (r *RealDebrid) DeleteTorrent(torrent *Torrent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RealDebrid) DownloadLink(torrent *Torrent) error {
|
func (r *RealDebrid) GetDownloadLinks(torrent *Torrent) error {
|
||||||
|
url := fmt.Sprintf("%s/unrestrict/link/", r.Host)
|
||||||
|
downloadLinks := make([]TorrentDownloadLinks, 0)
|
||||||
|
for _, link := range torrent.Links {
|
||||||
|
if link == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
payload := gourl.Values{
|
||||||
|
"link": {link},
|
||||||
|
}
|
||||||
|
resp, err := r.client.MakeRequest(http.MethodPost, url, strings.NewReader(payload.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var data structs.RealDebridUnrestrictResponse
|
||||||
|
if err = json.Unmarshal(resp, &data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
download := TorrentDownloadLinks{
|
||||||
|
Link: data.Link,
|
||||||
|
Filename: data.Filename,
|
||||||
|
DownloadLink: data.Download,
|
||||||
|
}
|
||||||
|
downloadLinks = append(downloadLinks, download)
|
||||||
|
}
|
||||||
|
torrent.DownloadLinks = downloadLinks
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,4 +93,15 @@ type RealDebridTorrentInfo struct {
|
|||||||
Seeders int `json:"seeders,omitempty"`
|
Seeders int `json:"seeders,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5e6e2e77fd3921a7903a41336c844cc409bf8788/14527C07BDFDDFC642963238BB6E7507B9742947/66A1CD1A5C7F4014877A51AC2620E857E3BB4D16
|
type RealDebridUnrestrictResponse struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
MimeType string `json:"mimeType"`
|
||||||
|
Filesize int64 `json:"filesize"`
|
||||||
|
Link string `json:"link"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Chunks int64 `json:"chunks"`
|
||||||
|
Crc int64 `json:"crc"`
|
||||||
|
Download string `json:"download"`
|
||||||
|
Streamable int `json:"streamable"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,25 +25,33 @@ type ArrHistorySchema struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Torrent struct {
|
type Torrent struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
InfoHash string `json:"info_hash"`
|
InfoHash string `json:"info_hash"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Folder string `json:"folder"`
|
Folder string `json:"folder"`
|
||||||
Filename string `json:"filename"`
|
Filename string `json:"filename"`
|
||||||
OriginalFilename string `json:"original_filename"`
|
OriginalFilename string `json:"original_filename"`
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
Bytes int64 `json:"bytes"` // Size of only the files that are downloaded
|
Bytes int64 `json:"bytes"` // Size of only the files that are downloaded
|
||||||
Magnet *common.Magnet `json:"magnet"`
|
Magnet *common.Magnet `json:"magnet"`
|
||||||
Files []TorrentFile `json:"files"`
|
Files []TorrentFile `json:"files"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Progress float64 `json:"progress"`
|
Progress float64 `json:"progress"`
|
||||||
Speed int64 `json:"speed"`
|
Speed int64 `json:"speed"`
|
||||||
Seeders int `json:"seeders"`
|
Seeders int `json:"seeders"`
|
||||||
|
Links []string `json:"links"`
|
||||||
|
DownloadLinks []TorrentDownloadLinks `json:"download_links"`
|
||||||
|
|
||||||
Debrid *Debrid
|
Debrid *Debrid
|
||||||
Arr *Arr
|
Arr *Arr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TorrentDownloadLinks struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Link string `json:"link"`
|
||||||
|
DownloadLink string `json:"download_link"`
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Torrent) GetSymlinkFolder(parent string) string {
|
func (t *Torrent) GetSymlinkFolder(parent string) string {
|
||||||
return filepath.Join(parent, t.Arr.Name, t.Folder)
|
return filepath.Join(parent, t.Arr.Name, t.Folder)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,13 +211,6 @@ func (item Item) getHash() string {
|
|||||||
}
|
}
|
||||||
infohash = hash
|
infohash = hash
|
||||||
if infohash == "" {
|
if infohash == "" {
|
||||||
//Get torrent file from http link
|
|
||||||
//Takes too long, not worth it
|
|
||||||
//magnet, err := common.OpenMagnetHttpURL(magnetLink)
|
|
||||||
//if err == nil && magnet != nil && magnet.InfoHash != "" {
|
|
||||||
// log.Printf("Magnet: %s", magnet.InfoHash)
|
|
||||||
//}
|
|
||||||
|
|
||||||
if strings.Contains(magnetLink, "http") {
|
if strings.Contains(magnetLink, "http") {
|
||||||
h, _ := common.GetInfohashFromURL(magnetLink)
|
h, _ := common.GetInfohashFromURL(magnetLink)
|
||||||
if h != "" {
|
if h != "" {
|
||||||
@@ -325,9 +318,7 @@ func (p *Proxy) Start() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy.OnRequest(
|
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.443$"))).HandleConnect(goproxy.AlwaysMitm)
|
||||||
goproxy.ReqHostMatches(regexp.MustCompile("^.443$")),
|
|
||||||
UrlMatches(regexp.MustCompile("^.*/api\\?t=(search|tvsearch|movie)(&.*)?$"))).HandleConnect(goproxy.AlwaysMitm)
|
|
||||||
proxy.OnResponse(
|
proxy.OnResponse(
|
||||||
UrlMatches(regexp.MustCompile("^.*/api\\?t=(search|tvsearch|movie)(&.*)?$")),
|
UrlMatches(regexp.MustCompile("^.*/api\\?t=(search|tvsearch|movie)(&.*)?$")),
|
||||||
goproxy.StatusCodeIs(http.StatusOK, http.StatusAccepted)).DoFunc(
|
goproxy.StatusCodeIs(http.StatusOK, http.StatusAccepted)).DoFunc(
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ func (q *QBit) RefreshArr(arr *debrid.Arr) {
|
|||||||
if reqErr == nil {
|
if reqErr == nil {
|
||||||
statusOk := strconv.Itoa(resp.StatusCode)[0] == '2'
|
statusOk := strconv.Itoa(resp.StatusCode)[0] == '2'
|
||||||
if statusOk {
|
if statusOk {
|
||||||
q.logger.Printf("Refreshed monitored downloads for %s", cmp.Or(arr.Name, arr.Host))
|
if q.debug {
|
||||||
|
q.logger.Printf("Refreshed monitored downloads for %s", cmp.Or(arr.Name, arr.Host))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if reqErr != nil {
|
if reqErr != nil {
|
||||||
|
|||||||
134
pkg/qbit/downloader.go
Normal file
134
pkg/qbit/downloader.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
package qbit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"goBlack/common"
|
||||||
|
"goBlack/pkg/debrid"
|
||||||
|
"goBlack/pkg/qbit/downloaders"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (q *QBit) processManualFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr *debrid.Arr) {
|
||||||
|
q.logger.Printf("Downloading %d files...", len(debridTorrent.DownloadLinks))
|
||||||
|
torrentPath := common.RemoveExtension(debridTorrent.OriginalFilename)
|
||||||
|
parent := common.RemoveInvalidChars(filepath.Join(q.DownloadFolder, debridTorrent.Arr.Name, torrentPath))
|
||||||
|
err := os.MkdirAll(parent, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
q.logger.Printf("Failed to create directory: %s\n", parent)
|
||||||
|
q.MarkAsFailed(torrent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
torrent.TorrentPath = torrentPath
|
||||||
|
q.downloadFiles(debridTorrent, parent)
|
||||||
|
q.UpdateTorrent(torrent, debridTorrent)
|
||||||
|
q.RefreshArr(arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *QBit) downloadFiles(debridTorrent *debrid.Torrent, parent string) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
semaphore := make(chan struct{}, 5)
|
||||||
|
client := downloaders.GetHTTPClient()
|
||||||
|
for _, link := range debridTorrent.DownloadLinks {
|
||||||
|
if link.DownloadLink == "" {
|
||||||
|
q.logger.Printf("No download link found for %s\n", link.Filename)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wg.Add(1)
|
||||||
|
semaphore <- struct{}{}
|
||||||
|
go func(link debrid.TorrentDownloadLinks) {
|
||||||
|
defer wg.Done()
|
||||||
|
defer func() { <-semaphore }()
|
||||||
|
err := downloaders.NormalHTTP(client, link.DownloadLink, filepath.Join(parent, link.Filename))
|
||||||
|
if err != nil {
|
||||||
|
q.logger.Printf("Error downloading %s: %v\n", link.DownloadLink, err)
|
||||||
|
} else {
|
||||||
|
q.logger.Printf("Downloaded %s successfully\n", link.DownloadLink)
|
||||||
|
}
|
||||||
|
}(link)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
q.logger.Printf("Downloaded all files for %s\n", debridTorrent.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *QBit) processSymlink(torrent *Torrent, debridTorrent *debrid.Torrent, arr *debrid.Arr) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
files := debridTorrent.Files
|
||||||
|
ready := make(chan debrid.TorrentFile, len(files))
|
||||||
|
|
||||||
|
q.logger.Printf("Checking %d files...", len(files))
|
||||||
|
rCloneBase := q.debrid.GetMountPath()
|
||||||
|
torrentPath, err := q.getTorrentPath(rCloneBase, debridTorrent) // /MyTVShow/
|
||||||
|
if err != nil {
|
||||||
|
q.MarkAsFailed(torrent)
|
||||||
|
q.logger.Printf("Error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
torrentSymlinkPath := filepath.Join(q.DownloadFolder, debridTorrent.Arr.Name, torrentPath) // /mnt/symlinks/{category}/MyTVShow/
|
||||||
|
err = os.MkdirAll(torrentSymlinkPath, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
q.logger.Printf("Failed to create directory: %s\n", torrentSymlinkPath)
|
||||||
|
q.MarkAsFailed(torrent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
torrentRclonePath := filepath.Join(rCloneBase, torrentPath)
|
||||||
|
for _, file := range files {
|
||||||
|
wg.Add(1)
|
||||||
|
go checkFileLoop(&wg, torrentRclonePath, file, ready)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(ready)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for f := range ready {
|
||||||
|
q.logger.Println("File is ready:", f.Path)
|
||||||
|
q.createSymLink(torrentSymlinkPath, torrentRclonePath, f)
|
||||||
|
}
|
||||||
|
// Update the torrent when all files are ready
|
||||||
|
torrent.TorrentPath = filepath.Base(torrentPath) // Quite important
|
||||||
|
q.UpdateTorrent(torrent, debridTorrent)
|
||||||
|
q.RefreshArr(arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *QBit) getTorrentPath(rclonePath string, debridTorrent *debrid.Torrent) (string, error) {
|
||||||
|
pathChan := make(chan string)
|
||||||
|
errChan := make(chan error)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
torrentPath := debridTorrent.GetMountFolder(rclonePath)
|
||||||
|
if torrentPath != "" {
|
||||||
|
pathChan <- torrentPath
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case path := <-pathChan:
|
||||||
|
return path, nil
|
||||||
|
case err := <-errChan:
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *QBit) createSymLink(path string, torrentMountPath string, file debrid.TorrentFile) {
|
||||||
|
|
||||||
|
// Combine the directory and filename to form a full path
|
||||||
|
fullPath := filepath.Join(path, file.Name) // /mnt/symlinks/{category}/MyTVShow/MyTVShow.S01E01.720p.mkv
|
||||||
|
// Create a symbolic link if file doesn't exist
|
||||||
|
torrentFilePath := filepath.Join(torrentMountPath, file.Name) // debridFolder/MyTVShow/MyTVShow.S01E01.720p.mkv
|
||||||
|
err := os.Symlink(torrentFilePath, fullPath)
|
||||||
|
if err != nil {
|
||||||
|
q.logger.Printf("Failed to create symlink: %s\n", fullPath)
|
||||||
|
}
|
||||||
|
// Check if the file exists
|
||||||
|
if !common.FileReady(fullPath) {
|
||||||
|
q.logger.Printf("Symlink not ready: %s\n", fullPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
59
pkg/qbit/downloaders/fasthttp.go
Normal file
59
pkg/qbit/downloaders/fasthttp.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package downloaders
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetFastHTTPClient() *fasthttp.Client {
|
||||||
|
return &fasthttp.Client{
|
||||||
|
TLSConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
StreamResponseBody: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NormalFastHTTP(client *fasthttp.Client, url, filename string) error {
|
||||||
|
req := fasthttp.AcquireRequest()
|
||||||
|
resp := fasthttp.AcquireResponse()
|
||||||
|
defer fasthttp.ReleaseRequest(req)
|
||||||
|
defer fasthttp.ReleaseResponse(resp)
|
||||||
|
|
||||||
|
req.SetRequestURI(url)
|
||||||
|
req.Header.SetMethod(fasthttp.MethodGet)
|
||||||
|
|
||||||
|
if err := client.Do(req, resp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the response status code
|
||||||
|
if resp.StatusCode() != fasthttp.StatusOK {
|
||||||
|
return fmt.Errorf("unexpected status code: %d", resp.StatusCode())
|
||||||
|
}
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func(file *os.File) {
|
||||||
|
err := file.Close()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error closing file:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(file)
|
||||||
|
bodyStream := resp.BodyStream()
|
||||||
|
if bodyStream == nil {
|
||||||
|
// Write to memory and then to file
|
||||||
|
_, err := file.Write(resp.Body())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, err := io.Copy(file, bodyStream); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
55
pkg/qbit/downloaders/grab.go
Normal file
55
pkg/qbit/downloaders/grab.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package downloaders
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"github.com/cavaliergopher/grab/v3"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetGrabClient() *grab.Client {
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
}
|
||||||
|
return &grab.Client{
|
||||||
|
UserAgent: "qBitTorrent",
|
||||||
|
HTTPClient: &http.Client{
|
||||||
|
Transport: tr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NormalGrab(client *grab.Client, url, filename string) error {
|
||||||
|
req, err := grab.NewRequest(filename, url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp := client.Do(req)
|
||||||
|
if err := resp.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := time.NewTicker(2 * time.Second)
|
||||||
|
defer t.Stop()
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-t.C:
|
||||||
|
fmt.Printf(" %s: transferred %d / %d bytes (%.2f%%)\n",
|
||||||
|
resp.Filename,
|
||||||
|
resp.BytesComplete(),
|
||||||
|
resp.Size,
|
||||||
|
100*resp.Progress())
|
||||||
|
|
||||||
|
case <-resp.Done:
|
||||||
|
// download is complete
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := resp.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
44
pkg/qbit/downloaders/http.go
Normal file
44
pkg/qbit/downloaders/http.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package downloaders
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetHTTPClient() *http.Client {
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
return &http.Client{Transport: tr}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NormalHTTP(client *http.Client, url, filename string) error {
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Send the HTTP GET request
|
||||||
|
resp, err := client.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error downloading file:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Check server response
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("server returned non-200 status: %d %s", resp.StatusCode, resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the response body to file
|
||||||
|
_, err = io.Copy(file, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -7,12 +7,10 @@ import (
|
|||||||
|
|
||||||
func (q *QBit) handleVersion(w http.ResponseWriter, r *http.Request) {
|
func (q *QBit) handleVersion(w http.ResponseWriter, r *http.Request) {
|
||||||
_, _ = w.Write([]byte("v4.3.2"))
|
_, _ = w.Write([]byte("v4.3.2"))
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *QBit) handleWebAPIVersion(w http.ResponseWriter, r *http.Request) {
|
func (q *QBit) handleWebAPIVersion(w http.ResponseWriter, r *http.Request) {
|
||||||
_, _ = w.Write([]byte("2.7"))
|
_, _ = w.Write([]byte("2.7"))
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *QBit) handlePreferences(w http.ResponseWriter, r *http.Request) {
|
func (q *QBit) handlePreferences(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package qbit
|
package qbit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -36,6 +37,8 @@ func (q *QBit) handleTorrentsAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSymlink := strings.ToLower(r.FormValue("sequentialDownload")) != "true"
|
||||||
|
q.logger.Printf("isSymlink: %v\n", isSymlink)
|
||||||
urls := r.FormValue("urls")
|
urls := r.FormValue("urls")
|
||||||
category := r.FormValue("category")
|
category := r.FormValue("category")
|
||||||
|
|
||||||
@@ -44,6 +47,8 @@ func (q *QBit) handleTorrentsAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
urlList = strings.Split(urls, "\n")
|
urlList = strings.Split(urls, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, "isSymlink", isSymlink)
|
||||||
|
|
||||||
for _, url := range urlList {
|
for _, url := range urlList {
|
||||||
if err := q.AddMagnet(ctx, url, category); err != nil {
|
if err := q.AddMagnet(ctx, url, category); err != nil {
|
||||||
q.logger.Printf("Error adding magnet: %v\n", err)
|
q.logger.Printf("Error adding magnet: %v\n", err)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ type QBit struct {
|
|||||||
storage *TorrentStorage
|
storage *TorrentStorage
|
||||||
debug bool
|
debug bool
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
arrs map[string]string // host:token (Used for refreshing in worker)
|
arrs sync.Map // host:token (Used for refreshing in worker)
|
||||||
RefreshInterval int
|
RefreshInterval int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ func NewQBit(config *common.Config, deb debrid.Service, cache *common.Cache) *QB
|
|||||||
debug: cfg.Debug,
|
debug: cfg.Debug,
|
||||||
storage: storage,
|
storage: storage,
|
||||||
logger: common.NewLogger("QBit", os.Stdout),
|
logger: common.NewLogger("QBit", os.Stdout),
|
||||||
arrs: make(map[string]string),
|
arrs: sync.Map{},
|
||||||
RefreshInterval: refreshInterval,
|
RefreshInterval: refreshInterval,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +63,9 @@ func NewQBit(config *common.Config, deb debrid.Service, cache *common.Cache) *QB
|
|||||||
func (q *QBit) Start() {
|
func (q *QBit) Start() {
|
||||||
|
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Use(middleware.Logger)
|
if q.debug {
|
||||||
|
r.Use(middleware.Logger)
|
||||||
|
}
|
||||||
r.Use(middleware.Recoverer)
|
r.Use(middleware.Recoverer)
|
||||||
|
|
||||||
q.AddRoutes(r)
|
q.AddRoutes(r)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func (q *QBit) authContext(next http.Handler) http.Handler {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
ctx = context.WithValue(r.Context(), "host", host)
|
ctx = context.WithValue(r.Context(), "host", host)
|
||||||
ctx = context.WithValue(ctx, "token", token)
|
ctx = context.WithValue(ctx, "token", token)
|
||||||
q.arrs[host] = token
|
q.arrs.Store(host, token)
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
101
pkg/qbit/qbit.go
101
pkg/qbit/qbit.go
@@ -8,10 +8,7 @@ import (
|
|||||||
"goBlack/pkg/debrid"
|
"goBlack/pkg/debrid"
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,7 +50,8 @@ func (q *QBit) Process(ctx context.Context, magnet *common.Magnet, category stri
|
|||||||
Token: ctx.Value("token").(string),
|
Token: ctx.Value("token").(string),
|
||||||
Host: ctx.Value("host").(string),
|
Host: ctx.Value("host").(string),
|
||||||
}
|
}
|
||||||
debridTorrent, err := debrid.ProcessQBitTorrent(q.debrid, magnet, arr)
|
isSymlink := ctx.Value("isSymlink").(bool)
|
||||||
|
debridTorrent, err := debrid.ProcessQBitTorrent(q.debrid, magnet, arr, isSymlink)
|
||||||
if err != nil || debridTorrent == nil {
|
if err != nil || debridTorrent == nil {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = fmt.Errorf("failed to process torrent")
|
err = fmt.Errorf("failed to process torrent")
|
||||||
@@ -64,7 +62,7 @@ func (q *QBit) Process(ctx context.Context, magnet *common.Magnet, category stri
|
|||||||
torrent.DebridTorrent = debridTorrent
|
torrent.DebridTorrent = debridTorrent
|
||||||
torrent.Name = debridTorrent.Name
|
torrent.Name = debridTorrent.Name
|
||||||
q.storage.AddOrUpdate(torrent)
|
q.storage.AddOrUpdate(torrent)
|
||||||
go q.processFiles(torrent, debridTorrent, arr) // We can send async for file processing not to delay the response
|
go q.processFiles(torrent, debridTorrent, arr, isSymlink) // We can send async for file processing not to delay the response
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,83 +95,22 @@ func (q *QBit) CreateTorrentFromMagnet(magnet *common.Magnet, category string) *
|
|||||||
return torrent
|
return torrent
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *QBit) processFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr *debrid.Arr) {
|
func (q *QBit) processFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr *debrid.Arr, isSymlink bool) {
|
||||||
var wg sync.WaitGroup
|
for debridTorrent.Status != "downloaded" {
|
||||||
files := debridTorrent.Files
|
progress := debridTorrent.Progress
|
||||||
ready := make(chan debrid.TorrentFile, len(files))
|
q.logger.Printf("Progress: %.2f%%", progress)
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
q.logger.Printf("Checking %d files...", len(files))
|
dbT, err := q.debrid.CheckStatus(debridTorrent, isSymlink)
|
||||||
rCloneBase := q.debrid.GetMountPath()
|
if err != nil {
|
||||||
torrentPath, err := q.getTorrentPath(rCloneBase, debridTorrent) // /MyTVShow/
|
q.logger.Printf("Error checking status: %v", err)
|
||||||
if err != nil {
|
q.MarkAsFailed(torrent)
|
||||||
q.MarkAsFailed(torrent)
|
return
|
||||||
q.logger.Printf("Error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
torrentSymlinkPath := filepath.Join(q.DownloadFolder, debridTorrent.Arr.Name, torrentPath) // /mnt/symlinks/{category}/MyTVShow/
|
|
||||||
err = os.MkdirAll(torrentSymlinkPath, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
q.logger.Printf("Failed to create directory: %s\n", torrentSymlinkPath)
|
|
||||||
q.MarkAsFailed(torrent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
torrentRclonePath := filepath.Join(rCloneBase, torrentPath)
|
|
||||||
for _, file := range files {
|
|
||||||
wg.Add(1)
|
|
||||||
go checkFileLoop(&wg, torrentRclonePath, file, ready)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
wg.Wait()
|
|
||||||
close(ready)
|
|
||||||
}()
|
|
||||||
|
|
||||||
for f := range ready {
|
|
||||||
q.logger.Println("File is ready:", f.Path)
|
|
||||||
q.createSymLink(torrentSymlinkPath, torrentRclonePath, f)
|
|
||||||
}
|
|
||||||
// Update the torrent when all files are ready
|
|
||||||
torrent.TorrentPath = filepath.Base(torrentPath) // Quite important
|
|
||||||
q.UpdateTorrent(torrent, debridTorrent)
|
|
||||||
q.RefreshArr(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *QBit) getTorrentPath(rclonePath string, debridTorrent *debrid.Torrent) (string, error) {
|
|
||||||
pathChan := make(chan string)
|
|
||||||
errChan := make(chan error)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
torrentPath := debridTorrent.GetMountFolder(rclonePath)
|
|
||||||
if torrentPath != "" {
|
|
||||||
pathChan <- torrentPath
|
|
||||||
return
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
}
|
||||||
}()
|
debridTorrent = dbT
|
||||||
|
}
|
||||||
select {
|
if isSymlink {
|
||||||
case path := <-pathChan:
|
q.processSymlink(torrent, debridTorrent, arr)
|
||||||
return path, nil
|
} else {
|
||||||
case err := <-errChan:
|
q.processManualFiles(torrent, debridTorrent, arr)
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *QBit) createSymLink(path string, torrentMountPath string, file debrid.TorrentFile) {
|
|
||||||
|
|
||||||
// Combine the directory and filename to form a full path
|
|
||||||
fullPath := filepath.Join(path, file.Name) // /mnt/symlinks/{category}/MyTVShow/MyTVShow.S01E01.720p.mkv
|
|
||||||
// Create a symbolic link if file doesn't exist
|
|
||||||
torrentFilePath := filepath.Join(torrentMountPath, file.Name) // debridFolder/MyTVShow/MyTVShow.S01E01.720p.mkv
|
|
||||||
err := os.Symlink(torrentFilePath, fullPath)
|
|
||||||
if err != nil {
|
|
||||||
q.logger.Printf("Failed to create symlink: %s\n", fullPath)
|
|
||||||
}
|
|
||||||
// Check if the file exists
|
|
||||||
if !common.FileReady(fullPath) {
|
|
||||||
q.logger.Printf("Symlink not ready: %s\n", fullPath)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ func (ts *TorrentStorage) Get(hash string) *Torrent {
|
|||||||
func (ts *TorrentStorage) GetAll(category string, filter string, hashes []string) []*Torrent {
|
func (ts *TorrentStorage) GetAll(category string, filter string, hashes []string) []*Torrent {
|
||||||
ts.mu.RLock()
|
ts.mu.RLock()
|
||||||
defer ts.mu.RUnlock()
|
defer ts.mu.RUnlock()
|
||||||
torrents := make([]*Torrent, 0, len(ts.torrents))
|
torrents := make([]*Torrent, 0)
|
||||||
for _, id := range ts.order {
|
for _, id := range ts.order {
|
||||||
torrent := ts.torrents[id]
|
torrent := ts.torrents[id]
|
||||||
if category != "" && torrent.Category != category {
|
if category != "" && torrent.Category != category {
|
||||||
|
|||||||
@@ -20,22 +20,27 @@ func (q *QBit) StartRefreshWorker(ctx context.Context) {
|
|||||||
q.logger.Println("Qbit Refresh Worker stopped")
|
q.logger.Println("Qbit Refresh Worker stopped")
|
||||||
return
|
return
|
||||||
case <-refreshTicker.C:
|
case <-refreshTicker.C:
|
||||||
q.RefreshArrs()
|
torrents := q.storage.GetAll("", "", nil)
|
||||||
|
if len(torrents) > 0 {
|
||||||
|
q.RefreshArrs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *QBit) RefreshArrs() {
|
func (q *QBit) RefreshArrs() {
|
||||||
torrents := q.storage.GetAll("", "", nil)
|
q.arrs.Range(func(key, value interface{}) bool {
|
||||||
if len(torrents) == 0 {
|
host, ok := key.(string)
|
||||||
return
|
token, ok2 := value.(string)
|
||||||
}
|
if !ok || !ok2 {
|
||||||
for host, token := range q.arrs {
|
return true
|
||||||
|
}
|
||||||
arr := &debrid.Arr{
|
arr := &debrid.Arr{
|
||||||
Name: "",
|
Name: "",
|
||||||
Token: token,
|
Token: token,
|
||||||
Host: host,
|
Host: host,
|
||||||
}
|
}
|
||||||
q.RefreshArr(arr)
|
q.RefreshArr(arr)
|
||||||
}
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user