11package api
22
33import (
4+ "context"
45 "encoding/json"
56 "fmt"
67 "net/http"
8+ neturl "net/url"
79 "strings"
10+ "time"
811
912 "github.com/JeremiahM37/librarr/internal/models"
13+ "github.com/JeremiahM37/librarr/internal/search"
1014)
1115
1216func (s * Server ) handleDownload (w http.ResponseWriter , r * http.Request ) {
@@ -27,6 +31,21 @@ func (s *Server) handleDownload(w http.ResponseWriter, r *http.Request) {
2731 s .db .LogActivity (username , "download_start" , req .Title , fmt .Sprintf ("Download started from %s" , req .Source ))
2832
2933 source := s .searchMgr .GetSource (req .Source )
34+ if req .MediaType == "" {
35+ if source != nil {
36+ switch source .SearchTab () {
37+ case "audiobook" , "manga" :
38+ req .MediaType = source .SearchTab ()
39+ }
40+ } else {
41+ switch req .Source {
42+ case "audiobook" :
43+ req .MediaType = "audiobook"
44+ case "prowlarr_manga" :
45+ req .MediaType = "manga"
46+ }
47+ }
48+ }
3049
3150 // Determine download type.
3251 downloadType := "direct"
@@ -44,13 +63,13 @@ func (s *Server) handleDownload(w http.ResponseWriter, r *http.Request) {
4463
4564 switch downloadType {
4665 case "torrent" :
47- s .handleTorrentDownload (w , req )
66+ s .handleTorrentDownload (w , r , req )
4867 default :
4968 s .handleDirectDownloadReq (w , req )
5069 }
5170}
5271
53- func (s * Server ) handleTorrentDownload (w http.ResponseWriter , req models.DownloadRequest ) {
72+ func (s * Server ) handleTorrentDownload (w http.ResponseWriter , r * http. Request , req models.DownloadRequest ) {
5473 if ! s .cfg .HasQBittorrent () {
5574 writeJSON (w , http .StatusBadRequest , map [string ]interface {}{
5675 "success" : false ,
@@ -59,7 +78,14 @@ func (s *Server) handleTorrentDownload(w http.ResponseWriter, req models.Downloa
5978 return
6079 }
6180
62- url := resolveTorrentURL (req )
81+ url , err := s .resolveTorrentURL (r .Context (), req , models.SearchResult {})
82+ if err != nil {
83+ writeJSON (w , http .StatusBadRequest , map [string ]interface {}{
84+ "success" : false ,
85+ "error" : fmt .Sprintf ("Failed to resolve AudioBookBay download: %v" , err ),
86+ })
87+ return
88+ }
6389 if url == "" {
6490 writeJSON (w , http .StatusBadRequest , map [string ]interface {}{
6591 "success" : false ,
@@ -90,7 +116,7 @@ func (s *Server) handleTorrentDownload(w http.ResponseWriter, req models.Downloa
90116 category = s .cfg .QBMangaCategory
91117 }
92118
93- err : = s .downloadMgr .StartTorrentDownload (url , req .Title , savePath , category )
119+ err = s .downloadMgr .StartTorrentDownload (url , req .Title , savePath , category )
94120 writeJSON (w , http .StatusOK , map [string ]interface {}{
95121 "success" : err == nil ,
96122 "title" : req .Title ,
@@ -173,7 +199,7 @@ func (s *Server) handleDownloadTorrent(w http.ResponseWriter, r *http.Request) {
173199 req .Title = "Unknown"
174200 }
175201 req .MediaType = "ebook"
176- s .handleTorrentDownload (w , req )
202+ s .handleTorrentDownload (w , r , req )
177203}
178204
179205func (s * Server ) handleDownloadAnnas (w http.ResponseWriter , r * http.Request ) {
@@ -202,7 +228,7 @@ func (s *Server) handleDownloadAudiobook(w http.ResponseWriter, r *http.Request)
202228 req .Title = "Unknown"
203229 }
204230 req .MediaType = "audiobook"
205- s .handleTorrentDownload (w , req )
231+ s .handleTorrentDownload (w , r , req )
206232}
207233
208234func (s * Server ) handleGetDownloads (w http.ResponseWriter , _ * http.Request ) {
@@ -256,22 +282,6 @@ func (s *Server) handleCheckDuplicate(w http.ResponseWriter, r *http.Request) {
256282 })
257283}
258284
259- func resolveTorrentURL (req models.DownloadRequest ) string {
260- if req .DownloadURL != "" {
261- return req .DownloadURL
262- }
263- if strings .HasPrefix (req .GUID , "magnet:" ) {
264- return req .GUID
265- }
266- if req .MagnetURL != "" {
267- return req .MagnetURL
268- }
269- if req .InfoHash != "" {
270- return fmt .Sprintf ("magnet:?xt=urn:btih:%s" , req .InfoHash )
271- }
272- return ""
273- }
274-
275285func extractSourceID (req models.DownloadRequest ) string {
276286 if req .MD5 != "" {
277287 return req .MD5
@@ -288,6 +298,92 @@ func extractSourceID(req models.DownloadRequest) string {
288298 return ""
289299}
290300
301+ var resolveABBMagnetFn = search .ResolveABBMagnet
302+
303+ func (s * Server ) resolveTorrentURL (ctx context.Context , req models.DownloadRequest , chosen models.SearchResult ) (string , error ) {
304+ if req .DownloadURL != "" {
305+ return req .DownloadURL , nil
306+ }
307+ if strings .HasPrefix (req .GUID , "magnet:" ) {
308+ return req .GUID , nil
309+ }
310+ if req .MagnetURL != "" {
311+ return req .MagnetURL , nil
312+ }
313+ if chosen .MagnetURL != "" {
314+ return chosen .MagnetURL , nil
315+ }
316+ if chosen .DownloadURL != "" {
317+ return chosen .DownloadURL , nil
318+ }
319+ if req .InfoHash != "" {
320+ return fmt .Sprintf ("magnet:?xt=urn:btih:%s" , req .InfoHash ), nil
321+ }
322+
323+ abbPath := req .AbbURL
324+ if abbPath == "" {
325+ abbPath = chosen .AbbURL
326+ }
327+ if abbPath == "" {
328+ return "" , nil
329+ }
330+
331+ resolved , err := s .resolveABBMagnet (ctx , abbPath )
332+ if err != nil {
333+ return "" , err
334+ }
335+ return resolved , nil
336+ }
337+
338+ func (s * Server ) resolveABBMagnet (ctx context.Context , abbURL string ) (string , error ) {
339+ if s .cfg == nil || s .cfg .Sources == nil {
340+ return "" , fmt .Errorf ("AudioBookBay sources not configured" )
341+ }
342+
343+ abbPath := normalizeABBPath (abbURL )
344+ if abbPath == "" {
345+ return "" , fmt .Errorf ("invalid AudioBookBay URL" )
346+ }
347+
348+ client := & http.Client {Timeout : 15 * time .Second }
349+ return resolveABBMagnetFn (ctx , client , s .cfg .UserAgent , abbPath , s .cfg .Sources .AudioBookBay .Mirrors , s .cfg .Sources .AudioBookBay .Trackers )
350+ }
351+
352+ func normalizeABBPath (raw string ) string {
353+ raw = strings .TrimSpace (raw )
354+ if raw == "" {
355+ return ""
356+ }
357+ if strings .HasPrefix (raw , "/" ) {
358+ return raw
359+ }
360+
361+ u , err := neturl .Parse (raw )
362+ if err != nil {
363+ if strings .HasPrefix (raw , "http://" ) || strings .HasPrefix (raw , "https://" ) {
364+ return ""
365+ }
366+ return "/" + strings .TrimLeft (raw , "/" )
367+ }
368+ if u .Scheme == "" && u .Host == "" {
369+ if strings .HasPrefix (u .Path , "/" ) {
370+ if u .RawQuery != "" {
371+ return u .Path + "?" + u .RawQuery
372+ }
373+ return u .Path
374+ }
375+ return "/" + strings .TrimLeft (u .Path , "/" )
376+ }
377+ path := u .EscapedPath ()
378+ if path == "" {
379+ path = "/"
380+ }
381+ if u .RawQuery != "" {
382+ path += "?" + u .RawQuery
383+ }
384+ return path
385+ }
386+
291387func errString (err error ) string {
292388 if err == nil {
293389 return ""
0 commit comments