torznabtoweb/main.go
2025-01-03 19:52:57 +02:00

153 lines
3.4 KiB
Go

package main
import (
"encoding/xml"
"fmt"
"io"
"log"
"net/http"
"os"
"path"
"strings"
"time"
"github.com/anacrolix/torrent"
)
// TorznabResponse represents the XML response structure
type TorznabResponse struct {
XMLName xml.Name `xml:"rss"`
Channel Channel `xml:"channel"`
}
type Channel struct {
Title string `xml:"title"`
Description string `xml:"description"`
Items []Item `xml:"item"`
}
type Item struct {
Title string `xml:"title"`
Link string `xml:"link"`
Size int64 `xml:"size"`
PubDate time.Time `xml:"pubDate"`
InfoHash string `xml:"infohash"`
MagnetURI string `xml:"magnetURI"`
}
// Indexer handles the book searching and downloading
type Indexer struct {
downloadDir string
client *torrent.Client
}
// NewIndexer creates a new indexer instance
func NewIndexer(downloadDir string) (*Indexer, error) {
cfg := torrent.NewDefaultClientConfig()
cfg.DataDir = downloadDir
client, err := torrent.NewClient(cfg)
if err != nil {
return nil, fmt.Errorf("failed to create torrent client: %v", err)
}
return &Indexer{
downloadDir: downloadDir,
client: client,
}, nil
}
// Search performs a search on oceanpdf for ebooks
func (i *Indexer) Search(query string) (*TorznabResponse, error) {
// Replace with actual oceanpdf search endpoint
searchURL := fmt.Sprintf("https://oceanofpdf.com/?q=%s", query)
resp, err := http.Get(searchURL)
if err != nil {
return nil, fmt.Errorf("search failed: %v", err)
}
defer resp.Body.Close()
var result TorznabResponse
if err := xml.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to decode response: %v", err)
}
return &result, nil
}
// Download starts downloading a book and creates a torrent
func (i *Indexer) Download(item Item) error {
// Download the book
resp, err := http.Get(item.Link)
if err != nil {
return fmt.Errorf("download failed: %v", err)
}
defer resp.Body.Close()
// Create file
fileName := path.Join(i.downloadDir, sanitizeFilename(item.Title)+".pdf")
out, err := os.Create(fileName)
if err != nil {
return fmt.Errorf("failed to create file: %v", err)
}
defer out.Close()
// Copy data
if _, err := io.Copy(out, resp.Body); err != nil {
return fmt.Errorf("failed to write file: %v", err)
}
// Create torrent
t, err := i.client.AddMagnet(item.MagnetURI)
if err != nil {
return fmt.Errorf("failed to add magnet: %v", err)
}
<-t.GotInfo()
t.DownloadAll()
return nil
}
// StartServer starts the Torznab API server
func (i *Indexer) StartServer(port int) error {
http.HandleFunc("/api", i.handleAPI)
return http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}
func (i *Indexer) handleAPI(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("q")
result, err := i.Search(query)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/xml")
xml.NewEncoder(w).Encode(result)
}
func sanitizeFilename(filename string) string {
// Remove invalid characters
return strings.Map(func(r rune) rune {
if strings.ContainsRune(`<>:"/\|?*`, r) {
return '-'
}
return r
}, filename)
}
func main() {
indexer, err := NewIndexer("./downloads")
if err != nil {
log.Fatal(err)
}
log.Printf("Starting Torznab server on port 9117...")
if err := indexer.StartServer(9117); err != nil {
log.Fatal(err)
}
}