Skip to content

Commit

Permalink
# This is a combination of 2 commits.
Browse files Browse the repository at this point in the history
# [toru] big update. moved old download command to 'wget' and made the download command interactively select and download multiple episodes to a specified directory

[toru] big update. moved old download command to 'wget' and made the download command interactively select and download multiple episodes to a specified directory

# This is the commit message #2:

[toru] big update. moved old download command to 'wget' and made the download command interactively select and download multiple episodes to a specified directory
  • Loading branch information
sweetbbak committed Aug 6, 2024
1 parent 65040fb commit 2bb0965
Show file tree
Hide file tree
Showing 6 changed files with 358 additions and 37 deletions.
183 changes: 166 additions & 17 deletions cmd/toru/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,191 @@ package main
import (
"context"
"fmt"
"log"
"os"
"time"

"github.com/anacrolix/torrent"
"github.com/pterm/pterm"
"github.com/sweetbbak/toru/pkg/libtorrent"
"github.com/sweetbbak/toru/pkg/search"
)

func DownloadTorrent(cl *libtorrent.Client) error {
var tfile string
//
//
//
//

if download.TorrentFile != "" {
tfile = download.TorrentFile
} else if download.Args.Query != "" {
tfile = download.Args.Query
func DownloadMain(cl *libtorrent.Client) error {
var outputName string
if download.Directory != "" {
outputName = string(download.Directory)
} else {
return fmt.Errorf("download: missing argument (magnet|torrent|url) OR --torrent flag")
outputName = "toru-media"
}

success, _ := pterm.DefaultSpinner.Start("getting torrent info")
// create download dir
if err := CreateOutput(outputName); err != nil {
return err
}

// set download dir lol
cl.SetDownloadDir(outputName)

// no need to serve torrents
// cl.SetServerOFF(true)
tmp := os.TempDir()
opt := libtorrent.SetDataDir(tmp)

if err := cl.Init(opt); err != nil {
return err
}

t, err := cl.AddTorrent(tfile)
torrents, err := DownloadMultiple(cl)
if err != nil {
return err
}

success.Success("Success!")
// Create a multi printer for managing multiple printers
multi := pterm.DefaultMultiPrinter

var pbars []*pterm.ProgressbarPrinter

for _, t := range torrents {
pb, _ := pterm.DefaultProgressbar.WithTotal(100).WithWriter(multi.NewWriter()).Start(TruncateString(t.Name(), 30))
pbars = append(pbars, pb)
}

_, err = multi.Start()
if err != nil {
log.Println(err)
}

ctx, cancel := context.WithCancel(context.Background())

go func() {
Progress(t, ctx)
for {
select {
case <-ctx.Done():
multi.Stop()
return
default:
for i, t := range torrents {
pb := pbars[i]
pc := float64(t.BytesCompleted()) / float64(t.Length()) * 100
numpeers := len(t.PeerConns())
pb.Increment().Current = int(pc)
pb.UpdateTitle(fmt.Sprintf("peers [%02d]", numpeers))
time.Sleep(time.Millisecond * 5)
}
}
}
}()

t.DownloadAll()
if cl.TorrentClient.WaitAll() {
cancel()
return nil
for {
if cl.TorrentClient.WaitAll() {
cancel()
println("done!")
return nil
}
}

}

// TODO: create a function that just handles query building
func SearchAnime(q *Search, term string) (*search.Results, error) {
s := search.NewSearch()

// build the query
if q.Category != "" {
s.Category = q.Category
}
if q.Filter != "" {
s.Filter = q.Filter
}
if q.SortBy != "" {
s.SortBy = q.SortBy
}
if q.SortOrder != "" {
s.SortOrder = q.SortOrder
}
if q.User != "" {
s.User = q.User
}
if q.Args.Query != "" {
s.Args.Query = q.Args.Query
}
if term != "" {
s.Args.Query = q.Args.Query
}
if q.Page != 0 {
s.Page = q.Page
}

if q.Latest {
s = &search.Search{
SortBy: "id",
SortOrder: "desc",
Page: 1,
Category: "subs",
}
}

if options.Proxy != "" {
s.ProxyURL = options.Proxy
}

// make the request for results to nyaa.si
m, err := s.Query()
if err != nil {
return nil, err
}

return m, nil
}

func DownloadMultiple(cl *libtorrent.Client) ([]*torrent.Torrent, error) {
q := &Search{
SortBy: download.SortBy,
SortOrder: download.SortOrder,
User: download.User,
Filter: download.Filter,
Page: download.Page,
Latest: download.Latest,
Category: download.Category,
}

q.Args.Query = download.Query

m, err := SearchAnime(q, download.Query)
if err != nil {
return nil, err
}

choices, err := fzfMenuMulti(m.Media)
if err != nil {
return nil, err
}

var torrents []*torrent.Torrent
for _, item := range choices {
t, err := cl.AddTorrent(item.Magnet)
if err != nil {
log.Println(err)
}

t.DownloadAll()
torrents = append(torrents, t)
}

return torrents, nil
}

func CreateOutput(dir string) error {
_, err := os.Stat(dir)
if err == nil {
return err
} else {
cancel()
return fmt.Errorf("Unable to completely download torrent: %s", t.Name())
return os.MkdirAll(dir, 0o755)
}
}
41 changes: 30 additions & 11 deletions cmd/toru/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,55 @@ type Completions struct {

// Streaming options
type Stream struct {
Magnet string `short:"m" long:"magnet" description:"stream directly from the provided torrent magnet link"`
TorrentFile string `short:"t" long:"torrent" description:"stream directly from the provided torrent file or torrent URL"`
Remove bool ` long:"rm" description:"remove cached files after exiting"`
Latest bool `short:"l" long:"latest" description:"view the latest anime and select an episode"`
FromJson flags.Filename `short:"j" long:"from-json" description:"resume selection from prior search saved as json [see: toru search --help]"`
Magnet string `short:"m" long:"magnet" description:"stream directly from the provided torrent magnet link"`
TorrentFile string `short:"t" long:"torrent" description:"stream directly from the provided torrent file or torrent URL"`
Remove bool `long:"rm" description:"remove cached files after exiting"`
Latest bool `short:"l" long:"latest" description:"view the latest anime and select an episode"`
FromJson flags.Filename `short:"j" long:"from-json" description:"resume selection from prior search saved as json [see: toru search --help]"`

// optional magnet link or torrent file as a trailing argument instead of explicitly defined
Args struct {
Query string
} `positional-args:"yes" positional-arg-name:"TORRENT"`
}

// Downloading options
// Downloading options by selecting items from a query
type Download struct {
Directory string `short:"d" long:"dir" description:"parent directory to download torrents to"`
TorrentFile string `short:"t" long:"torrent" description:"explicitly define torrent magnet|file|url to download"`
Directory string `short:"o" long:"output" description:"parent directory to download torrents to"`
Query string `short:"q" long:"query" description:"query to search for"`
SortBy string `short:"b" long:"sort-by" description:"sort results by a category [size|date|seeders|leechers|downloads]"`
SortOrder string `short:"O" long:"sort-order" description:"sort by ascending or descending: options [asc|desc]" choice:"asc"`
User string `short:"u" long:"user" description:"search for content by a user"`
Filter string `short:"f" long:"filter" description:"filter content. Options: [no-remakes|trusted]"`
Page uint `short:"p" long:"page" description:"which results page to display [default 1]"`
Stream bool `short:"s" long:"stream" description:"stream selected torrents after search"`
Multi bool `short:"m" long:"multi" description:"choose multiple torrents to queue for downloading or streaming"`
Latest bool `short:"n" long:"latest" description:"view the latest anime"`
Category string `short:"c" long:"category" description:"search torrents by a category: run [toru search --list] to see categories"`

// magnet link, torrent or torrent file url
Args struct {
Query string
} `positional-args:"yes" positional-arg-name:"TORRENT"`
} `positional-args:"yes" positional-arg-name:"QUERY"`
}

// Downloading options, moved to simple download section
type WGET struct {
Directory flags.Filename `short:"d" long:"dir" description:"parent directory to download torrents to"`
TorrentFile flags.Filename `short:"t" long:"torrent" description:"explicitly define torrent magnet|file|url to download"`

// magnet link, torrent or torrent file url
Args struct {
Query string
} `positional-args:"yes" positional-arg-name:"QUERY"`
}

type Latest struct{}

// Non-interactive CLI search options
type Search struct {
SortBy string `short:"b" long:"sort-by" description:"sort results by a category [size|date|seeders|leechers|downloads]"`
SortOrder string `short:"o" long:"sort-order" description:"sort by ascending or descending: options [asc|desc]" choice:"asc"`
SortOrder string `short:"o" long:"sort-order" description:"sort by ascending or descending: options [asc|desc]" choice:"asc"`
User string `short:"u" long:"user" description:"search for content by a user"`
Filter string `short:"f" long:"filter" description:"filter content. Options: [no-remakes|trusted]"`
Page uint `short:"p" long:"page" description:"which results page to display [default 1]"`
Expand All @@ -74,4 +94,3 @@ type Search struct {
Query string
} `positional-args:"yes" positional-arg-name:"QUERY"`
}

16 changes: 14 additions & 2 deletions cmd/toru/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var (
searchopts Search
download Download
completions Completions
wget WGET
latest Latest
)

Expand All @@ -41,11 +42,15 @@ func init() {
if err != nil {
log.Fatal(err)
}
d, err := parser.AddCommand("download", "download torrents", "download torrent from .torrent file, magnet or URL to a .torrent file", &download)
d, err := parser.AddCommand("download", "select one or many torrents to download", "download torrent from .torrent file, magnet or URL to a .torrent file", &download)
if err != nil {
log.Fatal(err)

}
_, err = parser.AddCommand("wget", "wget a torrent file", "wget a torrent file", &wget)
if err != nil {
log.Fatal(err)
}
_, err = parser.AddCommand("latest", "get the latest anime", "get the latest anime from nyaa.si", &latest)
if err != nil {
log.Fatal(err)
Expand Down Expand Up @@ -108,6 +113,13 @@ func main() {
cl.TorrentPort = options.TorrentPort
}

if parser.Active.Name == "download" {
if err := DownloadMain(cl); err != nil {
log.Fatal(err)
}
os.Exit(0)
}

if err := cl.Init(); err != nil {
log.Fatal(err)
}
Expand All @@ -121,7 +133,7 @@ func main() {
if err := runStream(cl); err != nil {
log.Fatal(err)
}
case "dl", "download":
case "wget":
if err := DownloadTorrent(cl); err != nil {
log.Fatal(err)
}
Expand Down
33 changes: 33 additions & 0 deletions cmd/toru/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,36 @@ func fzfMenu(m []nyaa.Media) (nyaa.Media, error) {

return m[idx], nil
}

func fzfMenuMulti(m []nyaa.Media) ([]nyaa.Media, error) {
idxs, err := fzf.FindMulti(
m,
func(i int) string {
return m[i].Name
},
fzf.WithPreviewWindow(func(i, width, height int) string {
if i == -1 {
return "lol"
}

return FormatMedia(m[i])

}),
)

var matches []nyaa.Media
for _, item := range idxs {
matches = append(matches, m[item])
}

// User has selected nothing
if err != nil {
if errors.Is(err, fzf.ErrAbort) {
os.Exit(0)
} else {
return nil, err
}
}

return matches, nil
}
Loading

0 comments on commit 2bb0965

Please sign in to comment.