Add more metadata
This commit is contained in:
@@ -60,14 +60,14 @@ func createSingleTorrentResponse(torrent realdebrid.Torrent, db *repo.Database)
|
|||||||
path := filepath.Join(currentPath, filenameV2)
|
path := filepath.Join(currentPath, filenameV2)
|
||||||
response := dav.File(
|
response := dav.File(
|
||||||
path,
|
path,
|
||||||
int(unrestrict.Filesize),
|
unrestrict.Filesize,
|
||||||
torrent.Added, // Assuming you want to use the torrent added time here
|
convertDate(torrent.Added),
|
||||||
link,
|
link,
|
||||||
)
|
)
|
||||||
responses = append(responses, response)
|
responses = append(responses, response)
|
||||||
} else {
|
} else {
|
||||||
// This link is not cached yet
|
// This link is not cached yet
|
||||||
unrestrictFn := func() (realdebrid.UnrestrictResponse, error) {
|
unrestrictFn := func() (*realdebrid.UnrestrictResponse, error) {
|
||||||
return realdebrid.UnrestrictCheck(os.Getenv("RD_TOKEN"), link)
|
return realdebrid.UnrestrictCheck(os.Getenv("RD_TOKEN"), link)
|
||||||
}
|
}
|
||||||
unrestrict := realdebrid.RetryUntilOk(unrestrictFn)
|
unrestrict := realdebrid.RetryUntilOk(unrestrictFn)
|
||||||
@@ -79,15 +79,14 @@ func createSingleTorrentResponse(torrent realdebrid.Torrent, db *repo.Database)
|
|||||||
Host: "",
|
Host: "",
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
} else {
|
|
||||||
db.Insert(torrent.Hash, torrent.Filename, *unrestrict)
|
|
||||||
}
|
}
|
||||||
|
db.Insert(torrent.Hash, torrent.Filename, *unrestrict)
|
||||||
filenameV2 := davextra.InsertLinkFragment(unrestrict.Filename, davextra.GetLinkFragment(unrestrict.Link))
|
filenameV2 := davextra.InsertLinkFragment(unrestrict.Filename, davextra.GetLinkFragment(unrestrict.Link))
|
||||||
path := filepath.Join(currentPath, filenameV2)
|
path := filepath.Join(currentPath, filenameV2)
|
||||||
response := dav.File(
|
response := dav.File(
|
||||||
path,
|
path,
|
||||||
int(unrestrict.Filesize),
|
unrestrict.Filesize,
|
||||||
torrent.Added,
|
convertDate(torrent.Added),
|
||||||
link,
|
link,
|
||||||
)
|
)
|
||||||
responses = append(responses, response)
|
responses = append(responses, response)
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func Router(mux *http.ServeMux, db *repo.Database) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
unrestrictFn := func() (realdebrid.UnrestrictResponse, error) {
|
unrestrictFn := func() (*realdebrid.UnrestrictResponse, error) {
|
||||||
return realdebrid.UnrestrictLink(os.Getenv("RD_TOKEN"), unrestrict.Link)
|
return realdebrid.UnrestrictLink(os.Getenv("RD_TOKEN"), unrestrict.Link)
|
||||||
}
|
}
|
||||||
resp := realdebrid.RetryUntilOk(unrestrictFn)
|
resp := realdebrid.RetryUntilOk(unrestrictFn)
|
||||||
|
|||||||
15
internal/dav/util.go
Normal file
15
internal/dav/util.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package dav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func convertDate(input string) string {
|
||||||
|
t, err := time.Parse(time.RFC3339, input)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error:", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return t.Format("Mon, 02 Jan 2006 15:04:05 GMT")
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ func Directory(path string) Response {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func File(path string, fileSize int, added string, link string) Response {
|
func File(path string, fileSize int64, added string, link string) Response {
|
||||||
return Response{
|
return Response{
|
||||||
Href: customPathEscape(path),
|
Href: customPathEscape(path),
|
||||||
Propstat: PropStat{
|
Propstat: PropStat{
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type PropStat struct {
|
|||||||
|
|
||||||
type Prop struct {
|
type Prop struct {
|
||||||
ResourceType ResourceType `xml:"d:resourcetype"`
|
ResourceType ResourceType `xml:"d:resourcetype"`
|
||||||
ContentLength int `xml:"d:getcontentlength"`
|
ContentLength int64 `xml:"d:getcontentlength"`
|
||||||
CreationDate string `xml:"d:creationdate"`
|
CreationDate string `xml:"d:creationdate"`
|
||||||
LastModified string `xml:"d:getlastmodified"`
|
LastModified string `xml:"d:getlastmodified"`
|
||||||
IsHidden int `xml:"d:ishidden"`
|
IsHidden int `xml:"d:ishidden"`
|
||||||
|
|||||||
@@ -6,9 +6,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func customPathEscape(input string) string {
|
func customPathEscape(input string) string {
|
||||||
|
// First, URL-escape the path segments
|
||||||
segments := strings.Split(input, "/")
|
segments := strings.Split(input, "/")
|
||||||
for i, segment := range segments {
|
for i, segment := range segments {
|
||||||
segments[i] = url.PathEscape(segment)
|
segments[i] = url.PathEscape(segment)
|
||||||
}
|
}
|
||||||
return strings.Join(segments, "/")
|
escapedPath := strings.Join(segments, "/")
|
||||||
|
// Convert any XML-escaped sequences back to URL-escaped sequences
|
||||||
|
escapedPath = strings.Replace(escapedPath, "&", "%26", -1) // for &
|
||||||
|
escapedPath = strings.Replace(escapedPath, "<", "%3C", -1) // for <
|
||||||
|
escapedPath = strings.Replace(escapedPath, ">", "%3E", -1) // for >
|
||||||
|
escapedPath = strings.Replace(escapedPath, "\"", "%22", -1) // for "
|
||||||
|
escapedPath = strings.Replace(escapedPath, "'", "%27", -1) // for '
|
||||||
|
return escapedPath
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,16 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func UnrestrictCheck(accessToken, link string) (UnrestrictResponse, error) {
|
func UnrestrictCheck(accessToken, link string) (*UnrestrictResponse, error) {
|
||||||
data := url.Values{}
|
data := url.Values{}
|
||||||
data.Set("link", link)
|
data.Set("link", link)
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", "https://api.real-debrid.com/rest/1.0/unrestrict/check", bytes.NewBufferString(data.Encode()))
|
req, err := http.NewRequest("POST", "https://api.real-debrid.com/rest/1.0/unrestrict/check", bytes.NewBufferString(data.Encode()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnrestrictResponse{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||||
@@ -25,35 +26,35 @@ func UnrestrictCheck(accessToken, link string) (UnrestrictResponse, error) {
|
|||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnrestrictResponse{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnrestrictResponse{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return UnrestrictResponse{}, fmt.Errorf("HTTP error: %s", resp.Status)
|
return nil, fmt.Errorf("HTTP error: %s", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
var response UnrestrictResponse
|
var response UnrestrictResponse
|
||||||
err = json.Unmarshal(body, &response)
|
err = json.Unmarshal(body, &response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnrestrictResponse{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return response, nil
|
return &response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnrestrictLink(accessToken, link string) (UnrestrictResponse, error) {
|
func UnrestrictLink(accessToken, link string) (*UnrestrictResponse, error) {
|
||||||
data := url.Values{}
|
data := url.Values{}
|
||||||
data.Set("link", link)
|
data.Set("link", link)
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", "https://api.real-debrid.com/rest/1.0/unrestrict/link", bytes.NewBufferString(data.Encode()))
|
req, err := http.NewRequest("POST", "https://api.real-debrid.com/rest/1.0/unrestrict/link", bytes.NewBufferString(data.Encode()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnrestrictResponse{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||||
@@ -62,33 +63,33 @@ func UnrestrictLink(accessToken, link string) (UnrestrictResponse, error) {
|
|||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnrestrictResponse{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnrestrictResponse{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return UnrestrictResponse{}, fmt.Errorf("HTTP error: %s", resp.Status)
|
return nil, fmt.Errorf("HTTP error: %s", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
var response UnrestrictResponse
|
var response UnrestrictResponse
|
||||||
err = json.Unmarshal(body, &response)
|
err = json.Unmarshal(body, &response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnrestrictResponse{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return response, nil
|
return &response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTorrents(accessToken string) ([]Torrent, error) {
|
func GetTorrents(accessToken string) ([]Torrent, error) {
|
||||||
baseURL := "https://api.real-debrid.com/rest/1.0/torrents"
|
baseURL := "https://api.real-debrid.com/rest/1.0/torrents"
|
||||||
var allTorrents []Torrent
|
var allTorrents []Torrent
|
||||||
page := 1
|
page := 1
|
||||||
limit := 2500
|
limit := 100
|
||||||
|
|
||||||
for {
|
for {
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
@@ -124,7 +125,7 @@ func GetTorrents(accessToken string) ([]Torrent, error) {
|
|||||||
|
|
||||||
allTorrents = append(allTorrents, torrents...)
|
allTorrents = append(allTorrents, torrents...)
|
||||||
|
|
||||||
totalCountHeader := resp.Header.Get("x-total-count")
|
totalCountHeader := "100" // resp.Header.Get("x-total-count")
|
||||||
totalCount, err := strconv.Atoi(totalCountHeader)
|
totalCount, err := strconv.Atoi(totalCountHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
@@ -140,25 +141,67 @@ func GetTorrents(accessToken string) ([]Torrent, error) {
|
|||||||
return deduplicateTorrents(allTorrents), nil
|
return deduplicateTorrents(allTorrents), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetTorrentInfo(accessToken, id string) (*Torrent, error) {
|
||||||
|
url := "https://api.real-debrid.com/rest/1.0/torrents/info/" + id
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("HTTP error: %s", resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response Torrent
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
func deduplicateTorrents(torrents []Torrent) []Torrent {
|
func deduplicateTorrents(torrents []Torrent) []Torrent {
|
||||||
mappedTorrents := make(map[string]Torrent)
|
mappedTorrents := make(map[string]Torrent)
|
||||||
|
|
||||||
for _, t := range torrents {
|
for _, t := range torrents {
|
||||||
if existing, ok := mappedTorrents[t.Filename]; ok {
|
torrentName := t.Filename
|
||||||
|
if existing, ok := mappedTorrents[torrentName]; ok {
|
||||||
if existing.Hash == t.Hash {
|
if existing.Hash == t.Hash {
|
||||||
// If hash is the same, combine the links
|
// If hash is the same, combine the links
|
||||||
existing.Links = append(existing.Links, t.Links...)
|
existing.ID += "," + t.ID
|
||||||
mappedTorrents[t.Filename] = existing
|
// existing.Links = append(existing.Links, t.Links...)
|
||||||
|
for _, link := range t.Links {
|
||||||
|
existing.Links = appendIfNotExists(existing.Links, link)
|
||||||
|
}
|
||||||
|
existing.Bytes += t.Bytes
|
||||||
|
existing.Added = moreRecent(existing.Added, t.Added)
|
||||||
|
mappedTorrents[torrentName] = existing
|
||||||
} else {
|
} else {
|
||||||
// If hash is different, delete old entry and create two new entries
|
// If hash is different, delete old entry and create two new entries
|
||||||
delete(mappedTorrents, t.Filename)
|
delete(mappedTorrents, torrentName)
|
||||||
newKey1 := fmt.Sprintf("%s - %s", t.Filename, t.Hash[:4])
|
newKey1 := fmt.Sprintf("%s - %s", torrentName, t.Hash[:4])
|
||||||
mappedTorrents[newKey1] = t
|
mappedTorrents[newKey1] = t
|
||||||
newKey2 := fmt.Sprintf("%s - %s", existing.Filename, existing.Hash[:4])
|
newKey2 := fmt.Sprintf("%s - %s", existing.Filename, existing.Hash[:4])
|
||||||
mappedTorrents[newKey2] = existing
|
mappedTorrents[newKey2] = existing
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mappedTorrents[t.Filename] = t
|
mappedTorrents[torrentName] = t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,3 +213,31 @@ func deduplicateTorrents(torrents []Torrent) []Torrent {
|
|||||||
|
|
||||||
return deduplicated
|
return deduplicated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func contains(slice []string, str string) bool {
|
||||||
|
for _, v := range slice {
|
||||||
|
if v == str {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendIfNotExists(slice []string, str string) []string {
|
||||||
|
if !contains(slice, str) {
|
||||||
|
slice = append(slice, str)
|
||||||
|
}
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
func moreRecent(time1, time2 string) string {
|
||||||
|
tTime1, err1 := time.Parse(time.RFC3339, time1)
|
||||||
|
tTime2, err2 := time.Parse(time.RFC3339, time2)
|
||||||
|
if err1 != nil || err2 != nil {
|
||||||
|
return time1
|
||||||
|
}
|
||||||
|
if tTime2.After(tTime1) {
|
||||||
|
time1 = time2
|
||||||
|
}
|
||||||
|
return time1
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,9 +14,19 @@ type UnrestrictResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Torrent struct {
|
type Torrent struct {
|
||||||
|
ID string `json:"id"`
|
||||||
Filename string `json:"filename"`
|
Filename string `json:"filename"`
|
||||||
Hash string `json:"hash"`
|
Hash string `json:"hash"`
|
||||||
Progress int `json:"progress"`
|
Progress int `json:"progress"`
|
||||||
Added string `json:"added"`
|
Added string `json:"added"`
|
||||||
|
Bytes int64 `json:"bytes"`
|
||||||
Links []string `json:"links"`
|
Links []string `json:"links"`
|
||||||
|
Files []File `json:"files,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Bytes int `json:"bytes"`
|
||||||
|
Selected int `json:"selected"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RetryUntilOk[T any](fn func() (T, error)) *T {
|
func RetryUntilOk[T any](fn func() (T, error)) T {
|
||||||
const initialDelay = 2 * time.Second
|
const initialDelay = 2 * time.Second
|
||||||
const maxDelay = 128 * time.Second
|
const maxDelay = 128 * time.Second
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
result, err := fn()
|
result, err := fn()
|
||||||
if err == nil {
|
if err == nil || strings.Contains(err.Error(), "404") {
|
||||||
return &result
|
return result
|
||||||
}
|
|
||||||
if strings.Contains(err.Error(), "404") {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
delay := time.Duration(math.Min(float64(initialDelay*time.Duration(math.Pow(2, float64(i)))), float64(maxDelay)))
|
delay := time.Duration(math.Min(float64(initialDelay*time.Duration(math.Pow(2, float64(i)))), float64(maxDelay)))
|
||||||
time.Sleep(delay)
|
time.Sleep(delay)
|
||||||
|
|||||||
Reference in New Issue
Block a user