2024-05-23 10:56:26 +02:00
package main
import (
"fmt"
"html/template"
"log"
"net/http"
2024-05-24 08:32:32 +02:00
"net/url"
2024-05-29 22:08:33 +02:00
"regexp"
2024-05-23 10:56:26 +02:00
"sort"
2024-05-29 22:08:33 +02:00
"strconv"
2024-05-24 08:32:32 +02:00
"strings"
2024-05-23 10:56:26 +02:00
"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
)
2024-08-09 09:55:41 +02:00
var fileResultsChan = make ( chan [ ] TorrentResult )
2024-05-23 10:56:26 +02:00
func initializeTorrentSites ( ) {
torrentGalaxy = NewTorrentGalaxy ( )
// nyaa = NewNyaa()
2024-05-29 22:08:33 +02:00
thePirateBay = NewThePirateBay ( )
2024-05-23 10:56:26 +02:00
// rutor = NewRutor()
}
2024-08-11 21:45:52 +02:00
func handleFileSearch ( w http . ResponseWriter , settings UserSettings , query , safe , lang string , page int ) {
2024-05-23 10:56:26 +02:00
startTime := time . Now ( )
2024-05-24 14:07:16 +02:00
cacheKey := CacheKey { Query : query , Page : page , Safe : safe == "true" , Lang : lang , Type : "file" }
combinedResults := getFileResultsFromCacheOrFetch ( cacheKey , query , safe , lang , page )
2024-05-23 10:56:26 +02:00
2024-05-24 14:07:16 +02:00
sort . Slice ( combinedResults , func ( i , j int ) bool { return combinedResults [ i ] . Seeders > combinedResults [ j ] . Seeders } )
2024-05-23 10:56:26 +02:00
elapsedTime := time . Since ( startTime )
funcMap := template . FuncMap {
2024-08-09 15:55:14 +02:00
"sub" : func ( a , b int ) int { return a - b } ,
"add" : func ( a , b int ) int { return a + b } ,
2024-05-23 10:56:26 +02:00
}
tmpl , err := template . New ( "files.html" ) . Funcs ( funcMap ) . ParseFiles ( "templates/files.html" )
if err != nil {
log . Printf ( "Failed to load template: %v" , err )
http . Error ( w , "Failed to load template" , http . StatusInternalServerError )
return
}
data := struct {
Results [ ] TorrentResult
Query string
Fetched string
Category string
Sort string
HasPrevPage bool
HasNextPage bool
Page int
Settings Settings
2024-08-11 21:45:52 +02:00
Theme string
2024-05-23 10:56:26 +02:00
} {
2024-05-24 14:07:16 +02:00
Results : combinedResults ,
2024-05-23 10:56:26 +02:00
Query : query ,
Fetched : fmt . Sprintf ( "%.2f" , elapsedTime . Seconds ( ) ) ,
Category : "all" ,
Sort : "seed" ,
HasPrevPage : page > 1 ,
2024-05-24 14:07:16 +02:00
HasNextPage : len ( combinedResults ) > 0 ,
2024-05-23 10:56:26 +02:00
Page : page ,
2024-08-11 21:45:52 +02:00
Settings : Settings { UxLang : lang , Safe : safe } , // Now this is painful, are there two Settings variables??
Theme : settings . Theme ,
2024-05-23 10:56:26 +02:00
}
2024-08-09 12:59:37 +02:00
// // Debugging: Print results before rendering template
// for _, result := range combinedResults {
// fmt.Printf("Title: %s, Magnet: %s\n", result.Title, result.Magnet)
// }
2024-05-24 08:32:32 +02:00
2024-05-23 10:56:26 +02:00
if err := tmpl . Execute ( w , data ) ; err != nil {
log . Printf ( "Failed to render template: %v" , err )
http . Error ( w , "Failed to render template" , http . StatusInternalServerError )
}
}
2024-05-24 14:07:16 +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 {
log . Println ( "Cache hit" )
cacheChan <- results
} else {
log . Println ( "Cache miss" )
cacheChan <- nil
}
} ( )
select {
case results := <- cacheChan :
if results == nil {
2024-08-09 09:55:41 +02:00
combinedResults = fetchFileResults ( query , safe , lang , page )
if len ( combinedResults ) > 0 {
resultsCache . Set ( cacheKey , convertToSearchResults ( combinedResults ) )
}
2024-05-24 14:07:16 +02:00
} else {
_ , torrentResults , _ := convertToSpecificResults ( results )
combinedResults = torrentResults
}
case <- time . After ( 2 * time . Second ) :
log . Println ( "Cache check timeout" )
2024-08-09 09:55:41 +02:00
combinedResults = fetchFileResults ( query , safe , lang , page )
if len ( combinedResults ) > 0 {
resultsCache . Set ( cacheKey , convertToSearchResults ( combinedResults ) )
}
2024-05-24 08:32:32 +02:00
}
2024-05-24 14:07:16 +02:00
return combinedResults
}
2024-08-09 09:55:41 +02:00
func fetchFileResults ( query , safe , lang string , page int ) [ ] TorrentResult {
2024-05-24 14:07:16 +02:00
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 {
2024-05-29 22:08:33 +02:00
r . Magnet = removeMagnetLink ( r . Magnet ) // Remove "magnet:", prehaps usless now?
2024-05-24 14:07:16 +02:00
results = append ( results , r )
}
}
2024-08-09 09:55:41 +02:00
if len ( results ) == 0 {
log . Printf ( "No file results found for query: %s, trying other nodes" , query )
2024-08-09 12:59:37 +02:00
results = tryOtherNodesForFileSearch ( query , safe , lang , page , [ ] string { hostID } )
2024-08-09 09:55:41 +02:00
}
2024-05-24 14:07:16 +02:00
return results
}
func removeMagnetLink ( magnet string ) string {
// Remove the magnet: prefix unconditionally
2024-05-29 22:08:33 +02:00
return strings . TrimPrefix ( magnet , "magnet:" )
2024-05-24 08:32:32 +02:00
}
2024-05-29 22:08:33 +02:00
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 {
log . Printf ( "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 :
log . Printf ( "Unknown unit: %s" , unit )
return 0
}
size , err := strconv . ParseFloat ( sizeStr , 64 )
if err != nil {
log . Printf ( "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
}
2024-08-09 12:59:37 +02:00
func contains ( slice [ ] string , item string ) bool {
for _ , v := range slice {
if v == item {
return true
}
}
return false
}