153 lines
3.4 KiB
Go
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)
|
|
}
|
|
}
|