- Revamp decypharr arch \n

- Add callback_ur, download_folder to addContent API \n
- Fix few bugs \n
- More declarative UI keywords
- Speed up repairs
- Few other improvements/bug fixes
This commit is contained in:
Mukhtar Akere
2025-06-02 12:57:36 +01:00
parent 1cd09239f9
commit 9c6c44d785
67 changed files with 1726 additions and 1464 deletions

View File

@@ -1,107 +1,16 @@
package qbit
import (
"context"
"encoding/base64"
"github.com/go-chi/chi/v5"
"github.com/sirrobot01/decypharr/internal/request"
"github.com/sirrobot01/decypharr/pkg/arr"
"github.com/sirrobot01/decypharr/pkg/service"
"net/http"
"path/filepath"
"strings"
)
func decodeAuthHeader(header string) (string, string, error) {
encodedTokens := strings.Split(header, " ")
if len(encodedTokens) != 2 {
return "", "", nil
}
encodedToken := encodedTokens[1]
bytes, err := base64.StdEncoding.DecodeString(encodedToken)
if err != nil {
return "", "", err
}
bearer := string(bytes)
colonIndex := strings.LastIndex(bearer, ":")
host := bearer[:colonIndex]
token := bearer[colonIndex+1:]
return host, token, nil
}
func (q *QBit) CategoryContext(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
category := strings.Trim(r.URL.Query().Get("category"), "")
if category == "" {
// Get from form
_ = r.ParseForm()
category = r.Form.Get("category")
if category == "" {
// Get from multipart form
_ = r.ParseMultipartForm(32 << 20)
category = r.FormValue("category")
}
}
ctx := context.WithValue(r.Context(), "category", strings.TrimSpace(category))
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func (q *QBit) authContext(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
host, token, err := decodeAuthHeader(r.Header.Get("Authorization"))
category := r.Context().Value("category").(string)
svc := service.GetService()
// Check if arr exists
a := svc.Arr.Get(category)
if a == nil {
downloadUncached := false
a = arr.New(category, "", "", false, false, &downloadUncached)
}
if err == nil {
host = strings.TrimSpace(host)
if host != "" {
a.Host = host
}
token = strings.TrimSpace(token)
if token != "" {
a.Token = token
}
}
svc.Arr.AddOrUpdate(a)
ctx := context.WithValue(r.Context(), "arr", a)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func HashesCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_hashes := chi.URLParam(r, "hashes")
var hashes []string
if _hashes != "" {
hashes = strings.Split(_hashes, "|")
}
if hashes == nil {
// Get hashes from form
_ = r.ParseForm()
hashes = r.Form["hashes"]
}
for i, hash := range hashes {
hashes[i] = strings.TrimSpace(hash)
}
ctx := context.WithValue(r.Context(), "hashes", hashes)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func (q *QBit) handleLogin(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
_arr := ctx.Value("arr").(*arr.Arr)
_arr := getArr(ctx)
if _arr == nil {
// No arr
_, _ = w.Write([]byte("Ok."))
@@ -122,7 +31,7 @@ func (q *QBit) handleWebAPIVersion(w http.ResponseWriter, r *http.Request) {
}
func (q *QBit) handlePreferences(w http.ResponseWriter, r *http.Request) {
preferences := NewAppPreferences()
preferences := getAppPreferences()
preferences.WebUiUsername = q.Username
preferences.SavePath = q.DownloadFolder
@@ -150,10 +59,10 @@ func (q *QBit) handleShutdown(w http.ResponseWriter, r *http.Request) {
func (q *QBit) handleTorrentsInfo(w http.ResponseWriter, r *http.Request) {
//log all url params
ctx := r.Context()
category := ctx.Value("category").(string)
category := getCategory(ctx)
filter := strings.Trim(r.URL.Query().Get("filter"), "")
hashes, _ := ctx.Value("hashes").([]string)
torrents := q.Storage.GetAllSorted(category, filter, hashes, "added_on", false)
hashes := getHashes(ctx)
torrents := q.storage.GetAllSorted(category, filter, hashes, "added_on", false)
request.JSONResponse(w, torrents, http.StatusOK)
}
@@ -180,9 +89,13 @@ func (q *QBit) handleTorrentsAdd(w http.ResponseWriter, r *http.Request) {
}
isSymlink := strings.ToLower(r.FormValue("sequentialDownload")) != "true"
debridName := r.FormValue("debrid")
category := r.FormValue("category")
_arr := getArr(ctx)
if _arr == nil {
_arr = arr.New(category, "", "", false, false, nil)
}
atleastOne := false
ctx = context.WithValue(ctx, "isSymlink", isSymlink)
// Handle magnet URLs
if urls := r.FormValue("urls"); urls != "" {
@@ -191,7 +104,7 @@ func (q *QBit) handleTorrentsAdd(w http.ResponseWriter, r *http.Request) {
urlList = append(urlList, strings.TrimSpace(u))
}
for _, url := range urlList {
if err := q.AddMagnet(ctx, url, category); err != nil {
if err := q.addMagnet(ctx, url, _arr, debridName, isSymlink); err != nil {
q.logger.Info().Msgf("Error adding magnet: %v", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
@@ -204,7 +117,7 @@ func (q *QBit) handleTorrentsAdd(w http.ResponseWriter, r *http.Request) {
if r.MultipartForm != nil && r.MultipartForm.File != nil {
if files := r.MultipartForm.File["torrents"]; len(files) > 0 {
for _, fileHeader := range files {
if err := q.AddTorrent(ctx, fileHeader, category); err != nil {
if err := q.addTorrent(ctx, fileHeader, _arr, debridName, isSymlink); err != nil {
q.logger.Info().Msgf("Error adding torrent: %v", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
@@ -224,14 +137,14 @@ func (q *QBit) handleTorrentsAdd(w http.ResponseWriter, r *http.Request) {
func (q *QBit) handleTorrentsDelete(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
hashes, _ := ctx.Value("hashes").([]string)
hashes := getHashes(ctx)
if len(hashes) == 0 {
http.Error(w, "No hashes provided", http.StatusBadRequest)
return
}
category := ctx.Value("category").(string)
category := getCategory(ctx)
for _, hash := range hashes {
q.Storage.Delete(hash, category, false)
q.storage.Delete(hash, category, false)
}
w.WriteHeader(http.StatusOK)
@@ -239,10 +152,10 @@ func (q *QBit) handleTorrentsDelete(w http.ResponseWriter, r *http.Request) {
func (q *QBit) handleTorrentsPause(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
hashes, _ := ctx.Value("hashes").([]string)
category := ctx.Value("category").(string)
hashes := getHashes(ctx)
category := getCategory(ctx)
for _, hash := range hashes {
torrent := q.Storage.Get(hash, category)
torrent := q.storage.Get(hash, category)
if torrent == nil {
continue
}
@@ -254,10 +167,10 @@ func (q *QBit) handleTorrentsPause(w http.ResponseWriter, r *http.Request) {
func (q *QBit) handleTorrentsResume(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
hashes, _ := ctx.Value("hashes").([]string)
category := ctx.Value("category").(string)
hashes := getHashes(ctx)
category := getCategory(ctx)
for _, hash := range hashes {
torrent := q.Storage.Get(hash, category)
torrent := q.storage.Get(hash, category)
if torrent == nil {
continue
}
@@ -269,10 +182,10 @@ func (q *QBit) handleTorrentsResume(w http.ResponseWriter, r *http.Request) {
func (q *QBit) handleTorrentRecheck(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
hashes, _ := ctx.Value("hashes").([]string)
category := ctx.Value("category").(string)
hashes := getHashes(ctx)
category := getCategory(ctx)
for _, hash := range hashes {
torrent := q.Storage.Get(hash, category)
torrent := q.storage.Get(hash, category)
if torrent == nil {
continue
}
@@ -315,7 +228,7 @@ func (q *QBit) handleCreateCategory(w http.ResponseWriter, r *http.Request) {
func (q *QBit) handleTorrentProperties(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
hash := r.URL.Query().Get("hash")
torrent := q.Storage.Get(hash, ctx.Value("category").(string))
torrent := q.storage.Get(hash, getCategory(ctx))
properties := q.GetTorrentProperties(torrent)
request.JSONResponse(w, properties, http.StatusOK)
@@ -324,22 +237,22 @@ func (q *QBit) handleTorrentProperties(w http.ResponseWriter, r *http.Request) {
func (q *QBit) handleTorrentFiles(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
hash := r.URL.Query().Get("hash")
torrent := q.Storage.Get(hash, ctx.Value("category").(string))
torrent := q.storage.Get(hash, getCategory(ctx))
if torrent == nil {
return
}
files := q.GetTorrentFiles(torrent)
files := q.getTorrentFiles(torrent)
request.JSONResponse(w, files, http.StatusOK)
}
func (q *QBit) handleSetCategory(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
category := ctx.Value("category").(string)
hashes, _ := ctx.Value("hashes").([]string)
torrents := q.Storage.GetAll("", "", hashes)
category := getCategory(ctx)
hashes := getHashes(ctx)
torrents := q.storage.GetAll("", "", hashes)
for _, torrent := range torrents {
torrent.Category = category
q.Storage.AddOrUpdate(torrent)
q.storage.AddOrUpdate(torrent)
}
request.JSONResponse(w, nil, http.StatusOK)
}
@@ -351,33 +264,33 @@ func (q *QBit) handleAddTorrentTags(w http.ResponseWriter, r *http.Request) {
return
}
ctx := r.Context()
hashes, _ := ctx.Value("hashes").([]string)
hashes := getHashes(ctx)
tags := strings.Split(r.FormValue("tags"), ",")
for i, tag := range tags {
tags[i] = strings.TrimSpace(tag)
}
torrents := q.Storage.GetAll("", "", hashes)
torrents := q.storage.GetAll("", "", hashes)
for _, t := range torrents {
q.SetTorrentTags(t, tags)
q.setTorrentTags(t, tags)
}
request.JSONResponse(w, nil, http.StatusOK)
}
func (q *QBit) handleRemoveTorrentTags(w http.ResponseWriter, r *http.Request) {
func (q *QBit) handleremoveTorrentTags(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
http.Error(w, "Failed to parse form data", http.StatusBadRequest)
return
}
ctx := r.Context()
hashes, _ := ctx.Value("hashes").([]string)
hashes := getHashes(ctx)
tags := strings.Split(r.FormValue("tags"), ",")
for i, tag := range tags {
tags[i] = strings.TrimSpace(tag)
}
torrents := q.Storage.GetAll("", "", hashes)
torrents := q.storage.GetAll("", "", hashes)
for _, torrent := range torrents {
q.RemoveTorrentTags(torrent, tags)
q.removeTorrentTags(torrent, tags)
}
request.JSONResponse(w, nil, http.StatusOK)
@@ -397,6 +310,6 @@ func (q *QBit) handleCreateTags(w http.ResponseWriter, r *http.Request) {
for i, tag := range tags {
tags[i] = strings.TrimSpace(tag)
}
q.AddTags(tags)
q.addTags(tags)
request.JSONResponse(w, nil, http.StatusOK)
}