2024-08-13 16:31:28 +02:00
package main
import (
"fmt"
"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 )
2024-10-16 22:51:13 +02:00
func init ( ) {
2024-08-13 16:31:28 +02:00
torrentGalaxy = NewTorrentGalaxy ( )
// nyaa = NewNyaa()
thePirateBay = NewThePirateBay ( )
// rutor = NewRutor()
}
func handleFileSearch ( w http . ResponseWriter , settings UserSettings , query string , page int ) {
startTime := time . Now ( )
2024-09-27 13:16:36 +02:00
cacheKey := CacheKey { Query : query , Page : page , Safe : settings . SafeSearch == "active" , Lang : settings . SearchLanguage , Type : "file" }
combinedResults := getFileResultsFromCacheOrFetch ( cacheKey , query , settings . SafeSearch , settings . SearchLanguage , page )
2024-08-13 16:31:28 +02:00
2024-10-08 22:11:06 +02:00
// Sort the results by the number of seeders
2024-08-13 16:31:28 +02:00
sort . Slice ( combinedResults , func ( i , j int ) bool { return combinedResults [ i ] . Seeders > combinedResults [ j ] . Seeders } )
elapsedTime := time . Since ( startTime )
2024-10-08 22:11:06 +02:00
// Prepare the data to pass to the template
data := map [ string ] interface { } {
"Results" : combinedResults ,
"Query" : query ,
"Fetched" : fmt . Sprintf ( "%.2f %s" , elapsedTime . Seconds ( ) , Translate ( "seconds" ) ) , // Time for fetching results
"Category" : "all" ,
"Sort" : "seed" ,
"Page" : page ,
"HasPrevPage" : page > 1 ,
"HasNextPage" : len ( combinedResults ) > 0 ,
"LanguageOptions" : languageOptions ,
"CurrentLang" : settings . SearchLanguage ,
"Theme" : settings . Theme ,
"Safe" : settings . SafeSearch ,
"IsThemeDark" : settings . IsThemeDark ,
2024-08-13 16:31:28 +02:00
}
2024-10-08 22:11:06 +02:00
// Render the template without measuring the time
renderTemplate ( w , "files.html" , data )
2024-08-13 16:31:28 +02:00
}
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
}