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
)
2025-02-21 19:47:55 +01:00
func initFileEngines ( ) {
torrentGalaxy = nil
thePirateBay = nil
2025-06-28 17:33:36 +02:00
nyaa = nil
2025-02-21 19:47:55 +01:00
// rutor = nil
for _ , engineName := range config . MetaSearch . Files {
switch engineName {
case "TorrentGalaxy" :
torrentGalaxy = NewTorrentGalaxy ( )
case "ThePirateBay" :
thePirateBay = NewThePirateBay ( )
2025-06-28 17:33:36 +02:00
case "Nyaa" :
nyaa = NewNyaa ( )
2025-02-21 19:47:55 +01:00
// case "Rutor":
// rutor = NewRutor()
}
}
2024-08-13 16:31:28 +02:00
}
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 ,
2025-04-24 16:56:41 +02:00
"Fetched" : FormatElapsedTime ( elapsedTime ) ,
2024-10-08 22:11:06 +02:00
"Category" : "all" ,
"Sort" : "seed" ,
"Page" : page ,
2025-01-05 20:27:13 +01:00
"HasPrevPage" : page >= 1 ,
2024-10-08 22:11:06 +02:00
"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 {
2024-11-26 07:46:03 +01:00
printDebug ( "Cache hit" )
2024-08-13 16:31:28 +02:00
cacheChan <- results
} else {
2024-11-26 07:46:03 +01:00
printDebug ( "Cache miss" )
2024-08-13 16:31:28 +02:00
cacheChan <- nil
}
} ( )
select {
case results := <- cacheChan :
if results == nil {
2024-11-26 07:46:03 +01:00
// Fetch only if the cache miss occurs and Crawler is enabled
2025-01-12 16:46:52 +01:00
if config . MetaSearchEnabled {
2024-11-26 07:46:03 +01:00
combinedResults = fetchFileResults ( query , safe , lang , page )
if len ( combinedResults ) > 0 {
resultsCache . Set ( cacheKey , convertToSearchResults ( combinedResults ) )
}
} else {
printDebug ( "Crawler disabled; skipping fetching." )
2024-08-13 16:31:28 +02:00
}
} else {
2025-04-18 11:22:42 +02:00
_ , torrentResults , _ , _ , _ := convertToSpecificResults ( results )
2024-08-13 16:31:28 +02:00
combinedResults = torrentResults
}
case <- time . After ( 2 * time . Second ) :
2024-11-26 07:46:03 +01:00
printDebug ( "Cache check timeout" )
2025-01-12 16:46:52 +01:00
if config . MetaSearchEnabled {
2024-11-26 07:46:03 +01:00
combinedResults = fetchFileResults ( query , safe , lang , page )
if len ( combinedResults ) > 0 {
resultsCache . Set ( cacheKey , convertToSearchResults ( combinedResults ) )
}
} else {
printDebug ( "Crawler disabled; skipping fetching." )
2024-08-13 16:31:28 +02:00
}
}
return combinedResults
}
func fetchFileResults ( query , safe , lang string , page int ) [ ] TorrentResult {
2024-11-26 07:46:03 +01:00
// If Crawler is disabled, skip fetching from torrent sites
2025-01-12 16:46:52 +01:00
if ! config . MetaSearchEnabled {
2024-11-26 07:46:03 +01:00
printInfo ( "Crawler is disabled; skipping torrent site fetching." )
return [ ] TorrentResult { }
}
2024-08-13 16:31:28 +02:00
sites := [ ] TorrentSite { torrentGalaxy , nyaa , thePirateBay , rutor }
2025-02-21 19:47:55 +01:00
var results [ ] TorrentResult
2024-08-13 16:31:28 +02:00
for _ , site := range sites {
if site == nil {
continue
}
res , err := site . Search ( query , "all" )
if err != nil {
2024-11-26 07:46:03 +01:00
printWarn ( "Error searching with %s: %v" , site . Name ( ) , err )
2024-08-13 16:31:28 +02:00
continue
}
for _ , r := range res {
2024-12-05 00:50:56 +01:00
r . Magnet = removeMagnetLink ( r . Magnet ) // Remove "magnet:", perhaps useless now?
2024-08-13 16:31:28 +02:00
results = append ( results , r )
}
}
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
}
2025-06-28 17:33:36 +02:00
re := regexp . MustCompile ( ` (?i)([\d.]+)\s*(K?M?G?T?i?B) ` )
2024-08-13 16:31:28 +02:00
matches := re . FindStringSubmatch ( sizeStr )
if len ( matches ) < 3 {
printWarn ( "Error parsing size: invalid format %s" , sizeStr )
return 0
}
2025-06-28 17:33:36 +02:00
numStr := matches [ 1 ]
2024-08-13 16:31:28 +02:00
unit := strings . ToUpper ( matches [ 2 ] )
var multiplier int64 = 1
switch unit {
2025-06-28 17:33:36 +02:00
case "B" :
multiplier = 1
case "KB" , "KIB" :
2024-08-13 16:31:28 +02:00
multiplier = 1024
2025-06-28 17:33:36 +02:00
case "MB" , "MIB" :
2024-08-13 16:31:28 +02:00
multiplier = 1024 * 1024
2025-06-28 17:33:36 +02:00
case "GB" , "GIB" :
2024-08-13 16:31:28 +02:00
multiplier = 1024 * 1024 * 1024
2025-06-28 17:33:36 +02:00
case "TB" , "TIB" :
2024-08-13 16:31:28 +02:00
multiplier = 1024 * 1024 * 1024 * 1024
default :
printWarn ( "Unknown unit: %s" , unit )
return 0
}
2025-06-28 17:33:36 +02:00
size , err := strconv . ParseFloat ( numStr , 64 )
2024-08-13 16:31:28 +02:00
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 {
2025-06-28 17:33:36 +02:00
const unit = 1024
if size < unit {
return fmt . Sprintf ( "%d B" , size )
2024-08-13 16:31:28 +02:00
}
2025-06-28 17:33:36 +02:00
div , exp := unit , 0
for n := size / unit ; n >= unit ; n /= unit {
div *= unit
exp ++
}
return fmt . Sprintf ( "%.1f %siB" , float64 ( size ) / float64 ( div ) , [ ] string { "K" , "M" , "G" , "T" , "P" , "E" } [ exp ] )
2024-08-13 16:31:28 +02:00
}
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
}