2024-08-13 16:31:28 +02:00
package main
import (
"fmt"
"html/template"
"net/http"
"net/url"
"regexp"
"sort"
"strconv"
"strings"
"time"
)
type Settings struct {
UxLang string
Safe string
}
type TorrentSite interface {
Name ( ) string
Search ( query string , category string ) ( [ ] TorrentResult , error )
}
var (
torrentGalaxy TorrentSite
nyaa TorrentSite
thePirateBay TorrentSite
rutor TorrentSite
)
var fileResultsChan = make ( chan [ ] TorrentResult )
func initializeTorrentSites ( ) {
torrentGalaxy = NewTorrentGalaxy ( )
// nyaa = NewNyaa()
thePirateBay = NewThePirateBay ( )
// rutor = NewRutor()
}
func handleFileSearch ( w http . ResponseWriter , settings UserSettings , query string , page int ) {
startTime := time . Now ( )
cacheKey := CacheKey { Query : query , Page : page , Safe : settings . SafeSearch == "true" , Lang : settings . Language , Type : "file" }
combinedResults := getFileResultsFromCacheOrFetch ( cacheKey , query , settings . SafeSearch , settings . Language , page )
sort . Slice ( combinedResults , func ( i , j int ) bool { return combinedResults [ i ] . Seeders > combinedResults [ j ] . Seeders } )
elapsedTime := time . Since ( startTime )
funcMap := template . FuncMap {
"sub" : func ( a , b int ) int { return a - b } ,
"add" : func ( a , b int ) int { return a + b } ,
}
tmpl , err := template . New ( "files.html" ) . Funcs ( funcMap ) . ParseFiles ( "templates/files.html" )
if err != nil {
printErr ( "Failed to load template: %v" , err )
http . Error ( w , "Failed to load template" , http . StatusInternalServerError )
return
}
data := struct {
2024-08-13 16:38:02 +02:00
Results [ ] TorrentResult
Query string
Fetched string
Category string
Sort string
Page int
HasPrevPage bool
HasNextPage bool
LanguageOptions [ ] LanguageOption
CurrentLang string
Theme string
Safe string
2024-08-13 16:31:28 +02:00
} {
2024-08-13 16:38:02 +02:00
Results : combinedResults ,
Query : query ,
Fetched : fmt . Sprintf ( "%.2f seconds" , elapsedTime . Seconds ( ) ) ,
Category : "all" ,
Sort : "seed" ,
Page : page ,
HasPrevPage : page > 1 ,
HasNextPage : len ( combinedResults ) > 0 ,
LanguageOptions : languageOptions ,
CurrentLang : settings . Language ,
Theme : settings . Theme ,
Safe : settings . SafeSearch ,
2024-08-13 16:31:28 +02:00
}
// // Debugging: Print results before rendering template
// for _, result := range combinedResults {
// fmt.Printf("Title: %s, Magnet: %s\n", result.Title, result.Magnet)
// }
if err := tmpl . Execute ( w , data ) ; err != nil {
printErr ( "Failed to render template: %v" , err )
http . Error ( w , "Failed to render template" , http . StatusInternalServerError )
}
}
func getFileResultsFromCacheOrFetch ( cacheKey CacheKey , query , safe , lang string , page int ) [ ] TorrentResult {
cacheChan := make ( chan [ ] SearchResult )
var combinedResults [ ] TorrentResult
go func ( ) {
results , exists := resultsCache . Get ( cacheKey )
if exists {
printInfo ( "Cache hit" )
cacheChan <- results
} else {
printInfo ( "Cache miss" )
cacheChan <- nil
}
} ( )
select {
case results := <- cacheChan :
if results == nil {
combinedResults = fetchFileResults ( query , safe , lang , page )
if len ( combinedResults ) > 0 {
resultsCache . Set ( cacheKey , convertToSearchResults ( combinedResults ) )
}
} else {
_ , torrentResults , _ := convertToSpecificResults ( results )
combinedResults = torrentResults
}
case <- time . After ( 2 * time . Second ) :
printInfo ( "Cache check timeout" )
combinedResults = fetchFileResults ( query , safe , lang , page )
if len ( combinedResults ) > 0 {
resultsCache . Set ( cacheKey , convertToSearchResults ( combinedResults ) )
}
}
return combinedResults
}
func fetchFileResults ( query , safe , lang string , page int ) [ ] TorrentResult {
sites := [ ] TorrentSite { torrentGalaxy , nyaa , thePirateBay , rutor }
results := [ ] TorrentResult { }
for _ , site := range sites {
if site == nil {
continue
}
res , err := site . Search ( query , "all" )
if err != nil {
continue
}
for _ , r := range res {
r . Magnet = removeMagnetLink ( r . Magnet ) // Remove "magnet:", prehaps usless now?
results = append ( results , r )
}
}
if len ( results ) == 0 {
printWarn ( "No file results found for query: %s, trying other nodes" , query )
results = tryOtherNodesForFileSearch ( query , safe , lang , page , [ ] string { hostID } )
}
return results
}
func removeMagnetLink ( magnet string ) string {
// Remove the magnet: prefix unconditionally
return strings . TrimPrefix ( magnet , "magnet:" )
}
func parseInt ( s string ) int {
i , err := strconv . Atoi ( s )
if err != nil {
return 0
}
return i
}
func parseSize ( sizeStr string ) int64 {
sizeStr = strings . TrimSpace ( sizeStr )
if sizeStr == "" {
return 0
}
// Use regex to extract numeric value and unit separately
re := regexp . MustCompile ( ` (?i)([\d.]+)\s*([KMGT]?B) ` )
matches := re . FindStringSubmatch ( sizeStr )
if len ( matches ) < 3 {
printWarn ( "Error parsing size: invalid format %s" , sizeStr )
return 0
}
sizeStr = matches [ 1 ]
unit := strings . ToUpper ( matches [ 2 ] )
var multiplier int64 = 1
switch unit {
case "KB" :
multiplier = 1024
case "MB" :
multiplier = 1024 * 1024
case "GB" :
multiplier = 1024 * 1024 * 1024
case "TB" :
multiplier = 1024 * 1024 * 1024 * 1024
default :
printWarn ( "Unknown unit: %s" , unit )
return 0
}
size , err := strconv . ParseFloat ( sizeStr , 64 )
if err != nil {
printWarn ( "Error parsing size: %v" , err )
return 0
}
return int64 ( size * float64 ( multiplier ) )
}
// apparently this is needed so it can announce that magnet link is being used and people start seeding it, but I dont like the fact that I add trackers purposefully
func applyTrackers ( magnetLink string ) string {
if magnetLink == "" {
return ""
}
trackers := [ ] string {
"udp://tracker.openbittorrent.com:80/announce" ,
"udp://tracker.opentrackr.org:1337/announce" ,
"udp://tracker.coppersurfer.tk:6969/announce" ,
"udp://tracker.leechers-paradise.org:6969/announce" ,
}
for _ , tracker := range trackers {
magnetLink += "&tr=" + url . QueryEscape ( tracker )
}
return magnetLink
}
func formatSize ( size int64 ) string {
if size >= 1024 * 1024 * 1024 * 1024 {
return fmt . Sprintf ( "%.2f TB" , float64 ( size ) / ( 1024 * 1024 * 1024 * 1024 ) )
} else if size >= 1024 * 1024 * 1024 {
return fmt . Sprintf ( "%.2f GB" , float64 ( size ) / ( 1024 * 1024 * 1024 ) )
} else if size >= 1024 * 1024 {
return fmt . Sprintf ( "%.2f MB" , float64 ( size ) / ( 1024 * 1024 ) )
} else if size >= 1024 {
return fmt . Sprintf ( "%.2f KB" , float64 ( size ) / 1024 )
}
return fmt . Sprintf ( "%d B" , size )
}
func sanitizeFileName ( name string ) string {
// Replace spaces with dashes
sanitized := regexp . MustCompile ( ` \s+ ` ) . ReplaceAllString ( name , "-" )
// Remove any characters that are not alphanumeric, dashes, or parentheses
sanitized = regexp . MustCompile ( ` [^a-zA-Z0-9\-\(\)] ` ) . ReplaceAllString ( sanitized , "" )
return sanitized
}
func contains ( slice [ ] string , item string ) bool {
for _ , v := range slice {
if v == item {
return true
}
}
return false
}