2024-05-23 10:56:26 +02:00
package main
import (
2024-08-09 09:55:41 +02:00
"encoding/json"
2024-05-23 10:56:26 +02:00
"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()
}
func handleFileSearch ( w http . ResponseWriter , query , safe , lang string , page int ) {
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 {
"sub" : subtract ,
"add" : add ,
}
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-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-05-24 14:07:16 +02:00
Settings : Settings { UxLang : lang , Safe : safe } ,
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
}
2024-08-09 12:59:37 +02:00
func tryOtherNodesForFileSearch ( query , safe , lang string , page int , visitedNodes [ ] string ) [ ] TorrentResult {
2024-08-09 09:55:41 +02:00
for _ , nodeAddr := range peers {
2024-08-09 12:59:37 +02:00
if contains ( visitedNodes , nodeAddr ) {
continue // Skip nodes already visited
}
results , err := sendFileSearchRequestToNode ( nodeAddr , query , safe , lang , page , visitedNodes )
2024-08-09 09:55:41 +02:00
if err != nil {
log . Printf ( "Error contacting node %s: %v" , nodeAddr , err )
continue
}
if len ( results ) > 0 {
return results
}
}
return nil
}
2024-08-09 12:59:37 +02:00
func sendFileSearchRequestToNode ( nodeAddr , query , safe , lang string , page int , visitedNodes [ ] string ) ( [ ] TorrentResult , error ) {
visitedNodes = append ( visitedNodes , nodeAddr )
2024-08-09 09:55:41 +02:00
searchParams := struct {
2024-08-09 12:59:37 +02:00
Query string ` json:"query" `
Safe string ` json:"safe" `
Lang string ` json:"lang" `
Page int ` json:"page" `
ResponseAddr string ` json:"responseAddr" `
VisitedNodes [ ] string ` json:"visitedNodes" `
2024-08-09 09:55:41 +02:00
} {
Query : query ,
Safe : safe ,
Lang : lang ,
Page : page ,
ResponseAddr : fmt . Sprintf ( "http://localhost:%d/node" , config . Port ) ,
2024-08-09 12:59:37 +02:00
VisitedNodes : visitedNodes ,
2024-08-09 09:55:41 +02:00
}
msgBytes , err := json . Marshal ( searchParams )
if err != nil {
return nil , fmt . Errorf ( "failed to marshal search parameters: %v" , err )
}
msg := Message {
ID : hostID ,
Type : "search-file" ,
Content : string ( msgBytes ) ,
}
err = sendMessage ( nodeAddr , msg )
if err != nil {
return nil , fmt . Errorf ( "failed to send search request to node %s: %v" , nodeAddr , err )
}
// Wait for results
select {
case res := <- fileResultsChan :
return res , nil
case <- time . After ( 20 * time . Second ) :
return nil , fmt . Errorf ( "timeout waiting for results from node %s" , nodeAddr )
}
}
func handleFileResultsMessage ( msg Message ) {
var results [ ] TorrentResult
err := json . Unmarshal ( [ ] byte ( msg . Content ) , & results )
if err != nil {
log . Printf ( "Error unmarshalling file results: %v" , err )
return
}
log . Printf ( "Received file results: %+v" , results )
// Send results to fileResultsChan
go func ( ) {
fileResultsChan <- results
} ( )
2024-08-08 21:59:10 +02:00
}
2024-05-24 14:07:16 +02:00
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-23 10:56:26 +02:00
func subtract ( a , b int ) int {
return a - b
}
func add ( a , b int ) int {
return a + b
}
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
}