files search added (wip)

This commit is contained in:
partisan 2024-05-23 10:56:26 +02:00
parent 5f64173135
commit 4ccb9a51d4
14 changed files with 432 additions and 45 deletions

View file

@ -9,10 +9,10 @@
- [X] Forums results
- [x] HTML+CSS site (no JS version)
- [X] Results cache
- [X] Torrent results
## Pending Tasks
- [ ] Torrent results
- [ ] Website with JS version
- [ ] JS applets for results (such as calculator)
- [ ] Dynamic results loading as user scrolls

185
files-torrentgalaxy.go Normal file
View file

@ -0,0 +1,185 @@
package main
import (
"fmt"
"log"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"github.com/PuerkitoBio/goquery"
)
const TORRENTGALAXY_DOMAIN = "torrentgalaxy.to"
type TorrentGalaxy struct{}
func NewTorrentGalaxy() *TorrentGalaxy {
return &TorrentGalaxy{}
}
func (tg *TorrentGalaxy) Name() string {
return "torrentgalaxy"
}
func (tg *TorrentGalaxy) getCategoryCode(category string) string {
switch category {
case "all":
return ""
case "audiobook":
return "&c13=1"
case "movie":
return "&c3=1&c46=1&c45=1&c42=1&c4=1&c1=1"
case "tv":
return "&c41=1&c5=1&c11=1&c6=1&c7=1"
case "games":
return "&c43=1&c10=1"
case "software":
return "&c20=1&c21=1&c18=1"
case "anime":
return "&c28=1"
case "music":
return "&c28=1&c22=1&c26=1&c23=1&c25=1&c24=1"
case "xxx":
safeSearch := true // Replace with actual safe search status
if safeSearch {
return "ignore"
}
return "&c48=1&c35=1&c47=1&c34=1"
default:
return ""
}
}
func (tg *TorrentGalaxy) Search(query string, category string) ([]TorrentResult, error) {
categoryCode := tg.getCategoryCode(category)
if categoryCode == "ignore" {
return []TorrentResult{}, nil
}
searchURL := fmt.Sprintf("https://%s/torrents.php?search=%s%s#results", TORRENTGALAXY_DOMAIN, url.QueryEscape(query), categoryCode)
resp, err := http.Get(searchURL)
if err != nil {
return nil, fmt.Errorf("error making request to TorrentGalaxy: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("error parsing HTML: %w", err)
}
var results []TorrentResult
doc.Find("div.tgxtablerow").Each(func(i int, s *goquery.Selection) {
titleDiv := s.Find("div#click")
title := strings.TrimSpace(titleDiv.Text())
magnetLink, exists := s.Find("a[href^='magnet']").Attr("href")
if !exists {
log.Printf("No magnet link found for title: %s", title)
return
}
byteSize := parseSize(s.Find("span.badge-secondary").Text())
viewCount := parseInt(s.Find("font[color='orange']").Text())
seeder := parseInt(s.Find("font[color='green']").Text())
leecher := parseInt(s.Find("font[color='#ff0000']").Text())
result := TorrentResult{
URL: fmt.Sprintf("https://%s", TORRENTGALAXY_DOMAIN),
Seeders: seeder,
Leechers: leecher,
Magnet: applyTrackers(magnetLink),
Views: viewCount,
Size: formatSize(byteSize),
Title: title,
}
results = append(results, result)
})
return results, nil
}
func parseInt(s string) int {
s = strings.ReplaceAll(s, ",", "")
result, err := strconv.Atoi(s)
if err != nil {
log.Printf("Error parsing integer: %v", err)
return 0
}
return result
}
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))
}
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)
}

123
files.go Normal file
View file

@ -0,0 +1,123 @@
package main
import (
"fmt"
"html/template"
"log"
"net/http"
"sort"
"time"
)
type Settings struct {
UxLang string
Safe string
}
type TorrentSite interface {
Name() string
Search(query string, category string) ([]TorrentResult, error)
}
type TorrentResult struct {
URL string
Seeders int
Leechers int
Magnet string
Views int
Size string
Title string
Error string
}
var (
torrentGalaxy TorrentSite
nyaa TorrentSite
thePirateBay TorrentSite
rutor TorrentSite
)
func initializeTorrentSites() {
torrentGalaxy = NewTorrentGalaxy()
// nyaa = NewNyaa()
// thePirateBay = NewThePirateBay()
// rutor = NewRutor()
}
func handleFileSearch(w http.ResponseWriter, query, safe, lang string, page int) {
startTime := time.Now()
settings := Settings{UxLang: lang, Safe: safe}
sites := []TorrentSite{torrentGalaxy, nyaa, thePirateBay, rutor}
results := []TorrentResult{}
allErrors := true
for _, site := range sites {
if site == nil {
continue
}
res, err := site.Search(query, "all")
if err != nil {
continue
}
if len(res) > 0 {
allErrors = false
}
results = append(results, res...)
}
if allErrors {
results = []TorrentResult{
{Error: "Results are currently unavailable, sorry. Please try again later."},
}
}
sort.Slice(results, func(i, j int) bool { return results[i].Seeders > results[j].Seeders })
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
}{
Results: results,
Query: query,
Fetched: fmt.Sprintf("%.2f", elapsedTime.Seconds()),
Category: "all",
Sort: "seed",
HasPrevPage: page > 1,
HasNextPage: len(results) > 0,
Page: page,
Settings: settings,
}
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)
}
}
func subtract(a, b int) int {
return a - b
}
func add(a, b int) int {
return a + b
}

View file

@ -1,4 +1,3 @@
// forums.go
package main
import (
@ -23,13 +22,13 @@ type ForumSearchResult struct {
func PerformRedditSearch(query string, safe string, page int) ([]ForumSearchResult, error) {
const (
pageSize = 25
baseURL = "https://www.reddit.com/"
baseURL = "https://www.reddit.com"
maxRetries = 5
initialBackoff = 2 * time.Second
)
var results []ForumSearchResult
searchURL := fmt.Sprintf("%ssearch.json?q=%s&limit=%d&start=%d", baseURL, url.QueryEscape(query), pageSize, page*pageSize)
searchURL := fmt.Sprintf("%s/search.json?q=%s&limit=%d&start=%d", baseURL, url.QueryEscape(query), pageSize, page*pageSize)
var resp *http.Response
var err error
@ -86,7 +85,7 @@ func PerformRedditSearch(query string, safe string, page int) ([]ForumSearchResu
}
publishedDate := time.Unix(int64(postData["created_utc"].(float64)), 0)
permalink := postData["permalink"].(string)
resultURL := baseURL + permalink
resultURL := fmt.Sprintf("%s%s", baseURL, permalink)
result := ForumSearchResult{
URL: resultURL,

View file

@ -51,7 +51,7 @@ func fetchImageResults(query string, safe, lang string, page int) ([]ImageSearch
const resultsPerPage = 50
var offset int
if page <= 1 {
offset = 0 // Assuming the API expects offset to start from 0 for the first page
offset = 0
} else {
offset = (page - 1) * resultsPerPage
}

View file

@ -1,4 +1,3 @@
// main.go
package main
import (
@ -72,6 +71,7 @@ func main() {
http.HandleFunc("/settings", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "templates/settings.html")
})
initializeTorrentSites()
fmt.Println("Server is listening on http://localhost:5000")
log.Fatal(http.ListenAndServe(":5000", nil))
}
@ -88,11 +88,13 @@ func handleSearch(w http.ResponseWriter, r *http.Request) {
case "image":
handleImageSearch(w, query, safe, lang, page)
case "video":
videoSearchEndpointHandler(w, r)
handleVideoSearch(w, query, safe, lang, page)
case "map":
handleMapSearch(w, query, safe)
case "forum":
handleForumsSearch(w, query, safe, lang, page)
case "file":
handleFileSearch(w, query, safe, lang, page)
case "text":
fallthrough
default:

2
run.sh
View file

@ -1,3 +1,3 @@
#!/bin/bash
go run main.go text-google.go images.go imageproxy.go video.go map.go text.go text-quant.go text-duckduckgo.go cache.go forums.go --debug
go run main.go text-google.go images.go imageproxy.go video.go map.go text.go text-quant.go text-duckduckgo.go cache.go forums.go files.go files-torrentgalaxy.go --debug

112
templates/files.html Normal file
View file

@ -0,0 +1,112 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Query}} - Ocásek</title>
<link rel="stylesheet" type="text/css" href="/static/css/style.css">
</head>
<body>
<form action="/search" id="prev-next-form" class="results-search-container" method="GET" autocomplete="off">
<h1 class="logomobile"><a class="no-decoration" href="./">Ocásek</a></h1>
<div class="wrapper-results">
<input type="text" name="q" value="{{ .Query }}" id="search-input" placeholder="Type to search..." />
<button id="search-wrapper-ico" class="material-icons-round" name="t" value="file">search</button>
<input type="submit" class="hide" name="t" value="file" />
</div>
<div class="sub-search-button-wrapper">
<div class="search-container-results-btn">
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="text">search</button>
<button name="t" value="text" class="clickable">Web</button>
</div>
<div class="search-container-results-btn">
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="image">image</button>
<button name="t" value="image" class="clickable">Images</button>
</div>
<div class="search-container-results-btn">
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="video">movie</button>
<button name="t" value="video" class="clickable">Videos</button>
</div>
<div class="search-container-results-btn">
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="forum">forum</button>
<button name="t" value="forum" class="clickable">Forums</button>
</div>
<div id="content" class="js-enabled">
<div class="search-container-results-btn">
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="map">map</button>
<button name="t" value="map" class="clickable">Maps</button>
</div>
</div>
<div class="search-container-results-btn">
<button id="sub-search-wrapper-ico" class="material-icons-round clickable search-active" name="t" value="file">share</button>
<button name="t" value="file" class="clickable search-active">Torrents</button>
</div>
</div>
</form>
<!-- <p class="fetched fetched_dif fetched_tor">{{ .Fetched }} seconds</p> -->
{{ if .Results }}
<form action="/search" class="torrent-sort" method="GET">
<input type="hidden" name="q" value="{{ .Query }}">
<input type="hidden" name="t" value="file">
<select class="torrent-settings" name="sort">
<option value="seed" {{ if eq .Sort "seed" }} selected {{ end }}>Number of Seeders</option>
<option value="leech" {{ if eq .Sort "leech" }} selected {{ end }}>Number of Leechers</option>
<option value="lth" {{ if eq .Sort "lth" }} selected {{ end }}>Size (Low to High)</option>
<option value="htl" {{ if eq .Sort "htl" }} selected {{ end }}>Size (High to Low)</option>
</select>
<select class="torrent-cat" name="cat">
<option value="all" {{ if eq .Category "all" }} selected {{ end }}>All Categories</option>
<option value="movie" {{ if eq .Category "movie" }} selected {{ end }}>Movies</option>
<option value="audiobook" {{ if eq .Category "audiobook" }} selected {{ end }}>Audiobooks</option>
<option value="tv" {{ if eq .Category "tv" }} selected {{ end }}>TV Shows</option>
<option value="games" {{ if eq .Category "games" }} selected {{ end }}>Games</option>
<option value="software" {{ if eq .Category "software" }} selected {{ end }}>Software</option>
<option value="anime" {{ if eq .Category "anime" }} selected {{ end }}>Anime</option>
<option value="music" {{ if eq .Category "music" }} selected {{ end }}>Music</option>
<!-- {{ if eq .Settings.Safe "inactive" }} -->
<option value="xxx" {{ if eq .Category "xxx" }} selected {{ end }}>XXX (18+)</option>
<!-- {{ end }} -->
</select>
<button type="submit" class="torrent-sort-save">Submit</button>
</form>
<div class="clean">
{{ range .Results }}
<div class="results">
{{ if .Error }}
<div class="error">{{ .Error }}</div>
{{ else }}
<a id="link" href="{{ .Magnet }}">{{ .URL }}</a>
<a class="torrent" href="{{ .Magnet }}"><h3>{{ .Title }}</h3></a>
<p class="stats">{{ if .Views }}{{ .Views }} views • {{ end }}{{ .Size }}</p>
<p class="publish__info"> Seeders: <span class="seeders">{{ .Seeders }}</span> | Leechers: <span class="leechers">{{ .Leechers }}</span></p>
{{ end }}
</div>
{{ end }}
</div>
<div class="prev-next prev-img">
<form action="/search" method="get">
<input type="hidden" name="q" value="{{ .Query }}">
<input type="hidden" name="t" value="file">
{{ if .HasPrevPage }}
<button type="submit" name="p" value="{{ sub .Page 1 }}">Previous</button>
{{ end }}
{{ if .HasNextPage }}
<button type="submit" name="p" value="{{ add .Page 1 }}">Next</button>
{{ end }}
</form>
</div>
{{ else }}
<div class="no-results-found">
Your search '{{ .Query }}' came back with no results.<br>
Try rephrasing your search term and/or recorrect any spelling mistakes.
</div>
{{ end }}
<script>
// Check if JavaScript is enabled and modify the DOM accordingly
document.getElementById('content').classList.remove('js-enabled');
</script>
</body>
</html>

View file

@ -38,8 +38,8 @@
</div>
</div>
<div class="search-container-results-btn">
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="torrent">share</button>
<button name="t" value="torrent" class="clickable">Torrents</button>
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="file">share</button>
<button name="t" value="file" class="clickable">Torrents</button>
</div>
</div>
</form>

View file

@ -1,4 +1,3 @@
// text-duckduckgo.go
package main
import (

View file

@ -1,4 +1,3 @@
// text-google.go
package main
import (

View file

@ -1,4 +1,3 @@
// text-qwant.go
package main
import (

16
text.go
View file

@ -173,22 +173,6 @@ func fetchAndCacheResults(query, safe, lang string, page, resultsPerPage int) []
return combinedResults
}
func paginateResults(results []TextSearchResult, page, resultsPerPage int) []TextSearchResult {
startIndex := (page - 1) * resultsPerPage
endIndex := startIndex + resultsPerPage
log.Printf("Paginating results: startIndex=%d, endIndex=%d, totalResults=%d", startIndex, endIndex, len(results))
if startIndex >= len(results) {
return []TextSearchResult{}
}
if endIndex > len(results) {
endIndex = len(results)
}
return results[startIndex:endIndex]
}
func sourceOrder(source string) int {
switch source {
case "Google":

View file

@ -7,7 +7,6 @@ import (
"log"
"net/http"
"net/url"
"strconv"
"sync"
"time"
)
@ -163,20 +162,6 @@ func makeHTMLRequest(query string) (*VideoAPIResponse, error) {
return nil, fmt.Errorf("all instances failed, last error: %v", lastError)
}
func videoSearchEndpointHandler(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("q")
safe := r.URL.Query().Get("safe") // Assuming "safe" is a query parameter
lang := r.URL.Query().Get("lang") // Assuming "lang" is a query parameter
pageStr := r.URL.Query().Get("page")
page, err := strconv.Atoi(pageStr)
if err != nil {
// Handle error, perhaps set page to a default value if it's not critical
page = 1 // Default page
}
handleVideoSearch(w, query, safe, lang, page)
}
// handleVideoSearch adapted from the Python `videoResults`, handles video search requests
func handleVideoSearch(w http.ResponseWriter, query, safe, lang string, page int) {
start := time.Now()