.
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | torpar
2 | TUI Client for Torrent Paradise
3 |
4 | ## Introduction
5 | `torpar` is TUI client for [Torrent Paradise](https://torrent-paradise.ml/) .
6 |
7 | [Torrent Paradise](https://torrent-paradise.ml/) is Decentralized DHT Torrent Search Site ([Source](https://github.com/urbanguacamole/torrent-paradise))
8 |
9 | ## Installation
10 |
11 | You can download binary for your OS from [Releases](https://github.com/varbhat/torpar/releases/latest) . Also , if you have [Go](https://golang.org/) installed , you can install `torpar` by typing this in terminal.
12 |
13 | ```bash
14 | go install github.com/varbhat/torpar@latest
15 | ```
16 |
17 | ## Features
18 |
19 | * Search the [Torrent Paradise](https://torrent-paradise.ml/)
20 | * Navigate the Search Results
21 | * Get Details of the Search Result
22 | * Copy Magnet Link of Search Result into Device Clipboard
23 | * Open Magnet Link in Preconfigured Application
24 | * Write Data to File
25 | * Navigate TUI interface very easily
26 | * Supports Most Platforms and Terminals (including tty)
27 |
28 | ## Usage
29 |
30 | ```
31 | Usage of torpar:
32 | -a, --apiurl string API Endpoint URL
33 | (default https://torrent-paradise.ml/api/search?q=)
34 | -f, --file string File Path to write Data(csv) into
35 | -q, --query string Search Query
36 | -l, --tlist string URL to tracker list
37 | (default https://newtrackon.com/api/stable)
38 | -t, --type int Type of Data(csv) to write into file
39 | 1 → Results
40 | 2 → Results with Magnet links (with trackers)
41 | 3 → Only Magnet links of Results
42 | 4 → Magnet link of Selected Torrent
43 | 5 → Data of Selected Torrent
44 | (default 1)
45 | -h, --help Help
46 | ```
47 |
48 | `torpar` is TUI and intuitive to understand .
49 |
50 | `torpar` also shows every navigation keys at each TUI views so that you can use `torpar` very easily.
51 |
52 | ## License
53 | [GPL-v3](LICENSE)
54 |
--------------------------------------------------------------------------------
/aboutui.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/jroimartin/gocui"
7 | )
8 |
9 | func aboutfunc(g *gocui.Gui) error {
10 |
11 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
12 | fmt.Println(err)
13 | }
14 |
15 | maxX, maxY := g.Size()
16 |
17 | // About Widget
18 | if aboutwid, err := g.SetView("aboutwid", -1, -1, maxX, maxY-2); err != nil {
19 | if err != gocui.ErrUnknownView {
20 | return err
21 | }
22 |
23 | if err := g.SetKeybinding("aboutwid", gocui.KeyArrowLeft, gocui.ModNone, backtoSearch); err != nil {
24 | return err
25 | }
26 | if err := g.SetKeybinding("aboutwid", gocui.KeyEnter, gocui.ModNone, backtoSearch); err != nil {
27 | return err
28 | }
29 | if err := g.SetKeybinding("aboutwid", gocui.KeyHome, gocui.ModNone, backtoSearch); err != nil {
30 | return err
31 | }
32 |
33 | if err := g.SetKeybinding("aboutwid", 'q', gocui.ModNone, quit); err != nil {
34 | return err
35 | }
36 | if _, err := g.SetCurrentView("aboutwid"); err != nil {
37 | return err
38 | }
39 | aboutwid.Frame = true
40 | aboutwid.Wrap = true
41 | fmt.Fprint(aboutwid, term_green, "About! ->\n\n", term_res)
42 | fmt.Fprint(aboutwid, term_green+"About Torpar ->\n"+term_res)
43 |
44 | abouttptext := `• TorPar is TUI client for Torrent Paradise
45 | • Torrent Paradise is Open-Source DHT Torrent Search Engine
46 | • TorPar is FLOSS and is licensed under GPLv3
47 | • Source code at https://github.com/varbhat/torpar`
48 |
49 | fmt.Fprint(aboutwid, abouttptext)
50 |
51 | fmt.Fprint(aboutwid, term_green, "\n\nAbout Torrent Paradise Search Engine ->\n", term_res)
52 |
53 | abouttpstext := `• Fresh and rich torrent index
54 | • New torrents identified quickly via multiple RSS feeds
55 | • Obscure torrents discovered through DHT
56 | • Seed/Leech counts constantly refreshed
57 | • privacy preserving, not-in-your-face ads
58 | • donate and vote on future features
59 | • Source Code at https://github.com/urbanguacamole/torrent-paradise
60 | • Send suggestions to urban-guacamole (at) protonmail.com
61 | • Want to report a copyright violation? See copyright at https://torrent-paradise.ml/copyright.html`
62 | fmt.Fprint(aboutwid, abouttpstext)
63 |
64 | aboutwid.MoveCursor(maxX-1, 0, true)
65 | }
66 | // Help Widget
67 | if abouthelpwid, err := g.SetView("abouthelpwid", -1, maxY-2, maxX, maxY); err != nil {
68 | if err != gocui.ErrUnknownView {
69 | return err
70 | }
71 | abouthelpwid.Frame = true
72 | abouthelpwid.Wrap = true
73 | fmt.Fprintln(abouthelpwid, "Press / ← to go back to Search , Ctrl-c / (q) to Quit")
74 | }
75 | return nil
76 | }
77 |
--------------------------------------------------------------------------------
/datawrite.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/csv" // csv encoding support
5 | "fmt"
6 | "os"
7 | "strconv"
8 |
9 | "github.com/jroimartin/gocui"
10 | )
11 |
12 | func torrentdatawrite(g *gocui.Gui) error {
13 |
14 | // Try to open specified file
15 | datafile, err := os.Create(datafilename)
16 | if err != nil {
17 | errorui = err
18 | g.SetManagerFunc(errorfunc)
19 | }
20 | defer datafile.Close()
21 |
22 | waitforme.Wait() // Wait for gentrackers to fetch trackers from trackerlist
23 |
24 | switch datatype {
25 |
26 | case 2:
27 | // Write CSV with torrent details(with magnet with trackers) to the file
28 | csvw := csv.NewWriter(datafile)
29 | csvw.Write([]string{"Id", "Name", "Size", "Seeds", "Leeches", "Infohash", "Magnet"})
30 | for cidno, ceacht := range torrents {
31 | csvw.Write(
32 | []string{
33 | strconv.Itoa(cidno),
34 | ceacht.Text,
35 | fmt.Sprintf("%f", ceacht.Length),
36 | strconv.Itoa(ceacht.Seeds),
37 | strconv.Itoa(ceacht.Leechs),
38 | ceacht.Id,
39 | magnetheader + ceacht.Id + trackerurl,
40 | })
41 | }
42 | csvw.Flush() // Flushing is necessary when csv.Writer.Write() is used
43 | if csverr := csvw.Error(); err != nil {
44 | errorui = csverr
45 | g.SetManagerFunc(errorfunc)
46 | }
47 |
48 | case 3:
49 | // Write Magnet Links to the file. That's it
50 | for _, ceacht := range torrents {
51 | if _, fprinterr := fmt.Fprintln(datafile, magnetheader+ceacht.Id+trackerurl); err != nil {
52 | errorui = fprinterr
53 | g.SetManagerFunc(errorfunc)
54 | }
55 | }
56 |
57 | case 4:
58 | // Write magnet link of selected torrent
59 | if _, fprinterr := fmt.Fprintln(datafile, magnetheader+torrents[selid].Id+trackerurl); err != nil {
60 | errorui = fprinterr
61 | g.SetManagerFunc(errorfunc)
62 | }
63 | case 5:
64 | // Write Torrent details of selected torrent
65 | if _, fprinterr := fmt.Fprint(datafile, "Name: "+torrents[selid].Text, "\nSize: ", torrents[selid].Length, "\nSeeds: ", torrents[selid].Seeds, "\nLeechs: ", torrents[selid].Leechs, "\nInfohash: ", torrents[selid].Id, "\n\nMagnet: \n\n", magnetheader+torrents[selid].Id+trackerurl); err != nil {
66 | errorui = fprinterr
67 | g.SetManagerFunc(errorfunc)
68 | }
69 | default:
70 | // Write CSV data of torrent to the file
71 | csvw := csv.NewWriter(datafile)
72 | csvw.Write([]string{"Id", "Name", "Size", "Seeds", "Leeches", "Infohash", "Magnet"})
73 | for cidno, ceacht := range torrents {
74 | csvw.Write(
75 | []string{
76 | strconv.Itoa(cidno),
77 | ceacht.Text,
78 | fmt.Sprintf("%f", ceacht.Length),
79 | strconv.Itoa(ceacht.Seeds),
80 | strconv.Itoa(ceacht.Leechs),
81 | ceacht.Id,
82 | magnetheader + ceacht.Id,
83 | })
84 | }
85 | csvw.Flush() // Flushing is necessary when csv.Writer.Write() is used
86 | if csverr := csvw.Error(); err != nil {
87 | errorui = csverr
88 | g.SetManagerFunc(errorfunc)
89 | }
90 | }
91 |
92 | g.Close()
93 | fmt.Println("Data has been written to file ", datafilename)
94 | os.Exit(1)
95 | return gocui.ErrQuit
96 | }
97 |
--------------------------------------------------------------------------------
/defs.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | // Declare Global Variables
8 | const magnetheader string = "magnet:?xt=urn:btih:"
9 |
10 | var apistr string = "https://torrent-paradise.ml/api/search?q="
11 | var trackerurl string // tracker list in url
12 | var torrents []Torrent // Array of Torrent
13 | var query string // Query String
14 | var trackerlisturl string // Tracker list URL
15 | var trackerquery string // used in flag assignation
16 | var selid int = -1 // Id of torrent selected
17 | var errorui error // error to be displayed in error ui
18 | var datafilename string // used in flag assignation
19 | var datatype int // used in flag assignation
20 | var waitforme sync.WaitGroup // used in some functions to wait for gentorrents to complete
21 |
22 | // Structure of Torrent
23 | type Torrent struct {
24 | Id string `json:"id"`
25 | Text string `json:"text"`
26 | Length float64 `json:"len"`
27 | Seeds int `json:"s"`
28 | Leechs int `json:"l"`
29 | }
30 |
31 | type AscTorrents []Torrent
32 |
33 | func (t AscTorrents) Len() int {
34 | return len(t)
35 | }
36 |
37 | func (t AscTorrents) Less(i, j int) bool {
38 | return t[i].Length < t[j].Length
39 | }
40 |
41 | func (t AscTorrents) Swap(i, j int) {
42 | t[i], t[j] = t[j], t[i]
43 | }
44 |
45 | type DeTorrents []Torrent
46 |
47 | func (t DeTorrents) Len() int {
48 | return len(t)
49 | }
50 |
51 | func (t DeTorrents) Less(i, j int) bool {
52 | return t[i].Length > t[j].Length
53 | }
54 |
55 | func (t DeTorrents) Swap(i, j int) {
56 | t[i], t[j] = t[j], t[i]
57 | }
58 |
59 | // color strings required to color text in terminal
60 | var (
61 | term_red string = "\u001b[0;31m" // Red color
62 | term_green string = "\u001b[0;32m" // Green color
63 | term_cyan string = "\u001b[0;36m" // Cyan color
64 | term_blue string = "\u001b[0;34m" // Blue color
65 | term_purp string = "\u001b[0;35m" // Purple color
66 | term_yell string = "\u001b[0;33m" // Yellow color
67 | term_res string = "\u001b[0;00m" // Color reset
68 | term_grey string = "\u001b[0;37m" // Color Grey
69 | )
70 |
--------------------------------------------------------------------------------
/errorui.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/jroimartin/gocui"
7 | )
8 |
9 | func errorfunc(g *gocui.Gui) error {
10 |
11 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
12 | fmt.Println(err)
13 | }
14 |
15 | maxX, maxY := g.Size()
16 |
17 | // Error Widget
18 | if errorwid, err := g.SetView("errorwid", -1, -1, maxX, maxY-2); err != nil {
19 | if err != gocui.ErrUnknownView {
20 | return err
21 | }
22 | if err := g.SetKeybinding("errorwid", gocui.KeyArrowLeft, gocui.ModNone, backtoSearch); err != nil {
23 | return err
24 | }
25 |
26 | if err := g.SetKeybinding("errorwid", gocui.KeyEnter, gocui.ModNone, backtoSearch); err != nil {
27 | return err
28 | }
29 | if err := g.SetKeybinding("errorwid", 'q', gocui.ModNone, quit); err != nil {
30 | return err
31 | }
32 | if _, err := g.SetCurrentView("errorwid"); err != nil {
33 | return err
34 | }
35 | errorwid.Frame = true
36 | errorwid.Wrap = true
37 | fmt.Fprint(errorwid, term_red, "Error! ->", term_res, "\n\n")
38 | fmt.Fprintln(errorwid, string(errorui.Error()))
39 | errorwid.MoveCursor(maxX-1, 0, true)
40 | }
41 | // Help Widget
42 | if errorhelpwid, err := g.SetView("errorhelpwid", -1, maxY-2, maxX, maxY); err != nil {
43 | if err != gocui.ErrUnknownView {
44 | return err
45 | }
46 | errorhelpwid.Frame = true
47 | errorhelpwid.Wrap = true
48 | fmt.Fprintln(errorhelpwid, "Press / ← to go back to Search , Ctrl-c / (q) to Quit")
49 | }
50 | return nil
51 | }
52 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/varbhat/torpar
2 |
3 | go 1.16
4 |
5 | require (
6 | github.com/atotto/clipboard v0.1.4
7 | github.com/cheynewallace/tabby v1.1.1
8 | github.com/jroimartin/gocui v0.5.0
9 | github.com/mattn/go-runewidth v0.0.13 // indirect
10 | github.com/nsf/termbox-go v1.1.1 // indirect
11 | github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
12 | github.com/spf13/pflag v1.0.5
13 | golang.org/x/tools v0.1.5 // indirect
14 | )
15 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
2 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
3 | github.com/cheynewallace/tabby v1.1.1 h1:JvUR8waht4Y0S3JF17G6Vhyt+FRhnqVCkk8l4YrOU54=
4 | github.com/cheynewallace/tabby v1.1.1/go.mod h1:Pba/6cUL8uYqvOc9RkyvFbHGrQ9wShyrn6/S/1OYVys=
5 | github.com/jroimartin/gocui v0.4.0 h1:52jnalstgmc25FmtGcWqa0tcbMEWS6RpFLsOIO+I+E8=
6 | github.com/jroimartin/gocui v0.4.0/go.mod h1:7i7bbj99OgFHzo7kB2zPb8pXLqMBSQegY7azfqXMkyY=
7 | github.com/jroimartin/gocui v0.5.0 h1:DCZc97zY9dMnHXJSJLLmx9VqiEnAj0yh0eTNpuEtG/4=
8 | github.com/jroimartin/gocui v0.5.0/go.mod h1:l7Hz8DoYoL6NoYnlnaX6XCNR62G7J5FfSW5jEogzaxE=
9 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
10 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
11 | github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
12 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
13 | github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=
14 | github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=
15 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
16 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
17 | github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
18 | github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
19 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
20 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
21 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
22 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
23 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
24 | golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
25 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
26 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
27 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
28 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
29 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
30 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
31 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
32 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
33 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
34 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
35 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
36 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
37 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
38 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
39 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
40 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
41 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
42 | golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
43 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
44 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
45 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
46 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
47 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
48 |
--------------------------------------------------------------------------------
/handlers.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sort"
6 | "strconv"
7 | "text/tabwriter"
8 |
9 | "github.com/cheynewallace/tabby"
10 | "github.com/jroimartin/gocui"
11 | )
12 |
13 | func quit(g *gocui.Gui, v *gocui.View) error {
14 | return gocui.ErrQuit
15 | }
16 |
17 | func cursorDown(g *gocui.Gui, v *gocui.View) error {
18 | if v != nil {
19 | cx, cy := v.Cursor()
20 | if _, lineerr := v.Line(cy + 2); lineerr != nil {
21 | return nil
22 | }
23 | if err := v.SetCursor(cx, cy+1); err != nil {
24 | ox, oy := v.Origin()
25 | if err := v.SetOrigin(ox, oy+1); err != nil {
26 | return err
27 | }
28 | }
29 | }
30 | return nil
31 | }
32 |
33 | func cursorUp(g *gocui.Gui, v *gocui.View) error {
34 | if v != nil {
35 | ox, oy := v.Origin()
36 | cx, cy := v.Cursor()
37 | if _, lineerr := v.Line(cy - 2); lineerr != nil {
38 | if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
39 | if err := v.SetOrigin(ox, oy-1); err != nil {
40 | return err
41 | }
42 | }
43 | if err := v.SetCursor(cx, cy); err != nil && oy > 0 {
44 | if err := v.SetOrigin(ox, oy); err != nil {
45 | return err
46 | }
47 | }
48 | return nil
49 | }
50 | if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
51 | if err := v.SetOrigin(ox, oy-1); err != nil {
52 | return err
53 | }
54 | }
55 | }
56 | return nil
57 | }
58 |
59 | func backtoSearch(g *gocui.Gui, v *gocui.View) error {
60 | g.SetManagerFunc(searchui)
61 | return nil
62 | }
63 |
64 | func backtoTorlist(g *gocui.Gui, v *gocui.View) error {
65 | g.SetManagerFunc(searchlisttor)
66 | return nil
67 | }
68 |
69 | func doSearch(g *gocui.Gui, v *gocui.View) error {
70 | dstv, _ := g.View("inputbox")
71 | query = dstv.Buffer()
72 | err := torparadise(g)
73 | return err
74 | }
75 |
76 | func doAbout(g *gocui.Gui, v *gocui.View) error {
77 | g.SetManagerFunc(aboutfunc)
78 | return nil
79 | }
80 |
81 | func sortAsc(g *gocui.Gui, v *gocui.View) error {
82 | maxX, _ := g.Size()
83 | sort.Sort(AscTorrents(torrents))
84 | v.Clear()
85 | if len(torrents) != 0 {
86 | maxlentl := int(float64(maxX) / 1.35)
87 | tw := tabwriter.NewWriter(v, 0, 0, 1, ' ', 0)
88 | t := tabby.NewCustom(tw)
89 | t.AddLine(term_res+"#", term_res+"Name", term_res+"Size", term_res+"Seeds", term_res+"Leeches"+term_res)
90 | for idno, eachtorrent := range torrents {
91 | t.AddLine(term_yell+strconv.Itoa(idno+1),
92 | term_cyan+maxstring(eachtorrent.Text, maxlentl),
93 | term_purp+fmt.Sprintf("%f", 0.000000001*eachtorrent.Length)+" GB",
94 | term_green+"S:"+strconv.Itoa(eachtorrent.Seeds),
95 | term_red+"L:"+strconv.Itoa(eachtorrent.Leechs)+term_res)
96 | }
97 | t.Print()
98 | }
99 | return nil
100 | }
101 |
102 | func sortDec(g *gocui.Gui, v *gocui.View) error {
103 | maxX, _ := g.Size()
104 | sort.Sort(DeTorrents(torrents))
105 | v.Clear()
106 | if len(torrents) != 0 {
107 | maxlentl := int(float64(maxX) / 1.35)
108 | tw := tabwriter.NewWriter(v, 0, 0, 1, ' ', 0)
109 | t := tabby.NewCustom(tw)
110 | t.AddLine(term_res+"#", term_res+"Name", term_res+"Size", term_res+"Seeds", term_res+"Leeches"+term_res)
111 | for idno, eachtorrent := range torrents {
112 | t.AddLine(term_yell+strconv.Itoa(idno+1),
113 | term_cyan+maxstring(eachtorrent.Text, maxlentl),
114 | term_purp+fmt.Sprintf("%f", 0.000000001*eachtorrent.Length)+" GB",
115 | term_green+"S:"+strconv.Itoa(eachtorrent.Seeds),
116 | term_red+"L:"+strconv.Itoa(eachtorrent.Leechs)+term_res)
117 | }
118 | t.Print()
119 | }
120 | return nil
121 | }
122 |
--------------------------------------------------------------------------------
/init.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "os"
6 |
7 | flag "github.com/spf13/pflag"
8 | )
9 |
10 | func init() {
11 | searchquery := flag.StringP("query", "q", "", "Search Query")
12 | trquery := flag.StringP("tlist", "l", "", "URL to tracker list\n (default https://newtrackon.com/api/stable)")
13 | datafilequery := flag.StringP("file", "f", "", "File Path to write Data(csv) into")
14 | datatypequery := flag.IntP("type", "t", 1, "Type of Data(csv) to write into file\n 1 → Results\n 2 → Results with Magnet links (with trackers)\n 3 → Only Magnet links of Results\n 4 → Magnet link of Selected Torrent\n 5 → Data of Selected Torrent\n")
15 | flag.ErrHelp = errors.New(" -h, --help Help\n\n" + os.Args[0] + " - Torrent Paradise TUI client.\nSource: https://github.com/varbhat/torpar\nTP: https://torrent-paradise.ml/\nTP Source: https://github.com/urbanguacamole/torrent-paradise\nDonate: https://torrent-paradise.ml/vote-and-donate.html\nAbout: https://torrent-paradise.ml/about.html")
16 | mainapiquery := flag.StringP("apiurl", "a", "", "API Endpoint URL\n (default https://torrent-paradise.ml/api/search?q=)")
17 |
18 | flag.Parse()
19 | query = *searchquery
20 | trackerquery = *trquery
21 | datafilename = *datafilequery
22 | datatype = *datatypequery
23 |
24 | // If specified , change API Endpoint
25 | if *mainapiquery != "" {
26 | apistr = *mainapiquery
27 | }
28 | waitforme.Add(1)
29 | }
30 |
--------------------------------------------------------------------------------
/listui.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "io/ioutil"
8 | "net/http"
9 | "net/url"
10 | "strconv"
11 | "strings"
12 | "text/tabwriter"
13 |
14 | "github.com/cheynewallace/tabby"
15 | "github.com/jroimartin/gocui"
16 | )
17 |
18 | func maxstring(str string, length int) string {
19 | strlen := len(str)
20 | if strlen > length {
21 | return str[:length]
22 | } else {
23 | return str
24 | }
25 | }
26 |
27 | func torparadise(g *gocui.Gui) error {
28 |
29 | if strings.TrimSpace(query) == "" {
30 | errorui = errors.New("No Query")
31 | g.SetManagerFunc(errorfunc)
32 | return nil
33 | }
34 |
35 | // construct API URL
36 | finalquery := apistr + url.QueryEscape(query)
37 |
38 | // Query the Torrent Paradise API to get result as response
39 | response, err := http.Get(finalquery)
40 | if err != nil {
41 | errorui = err
42 | g.SetManagerFunc(errorfunc)
43 | return nil
44 | }
45 |
46 | // Read JSON Response
47 | respbody, err := ioutil.ReadAll(response.Body)
48 | if err != nil {
49 | errorui = err
50 | g.SetManagerFunc(errorfunc)
51 | return nil
52 | }
53 |
54 | // Parse the json response into []Torrent
55 | err = json.Unmarshal([]byte(respbody), &torrents)
56 | if err != nil {
57 | errorui = err
58 | g.SetManagerFunc(errorfunc)
59 |
60 | }
61 |
62 | if len(torrents) == 0 {
63 | errorui = errors.New("No Results")
64 | g.SetManagerFunc(errorfunc)
65 | return nil
66 | }
67 | query = ""
68 |
69 | if len(datafilename) > 0 {
70 | switch datatype {
71 | case 4, 5:
72 | g.SetManagerFunc(searchlisttor)
73 | default:
74 | g.SetManagerFunc(torrentdatawrite)
75 | }
76 | } else {
77 | g.SetManagerFunc(searchlisttor)
78 | }
79 | return nil
80 | }
81 |
82 | func searchlisttor(g *gocui.Gui) error {
83 |
84 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
85 | fmt.Println(err)
86 | }
87 |
88 | maxX, maxY := g.Size()
89 |
90 | // Torrent List Box
91 | if tllist, err := g.SetView("tl", -1, -1, maxX, maxY-2); err != nil {
92 |
93 | if err != gocui.ErrUnknownView {
94 | return err
95 | }
96 |
97 | tllist.Highlight = true
98 | tllist.SelBgColor = gocui.ColorBlue
99 | tllist.SelFgColor = gocui.ColorWhite
100 | tllist.Wrap = true
101 | tllist.MoveCursor(maxX-1, 1, true)
102 |
103 | if err := g.SetKeybinding("tl", gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil {
104 | return err
105 | }
106 | if err := g.SetKeybinding("tl", gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil {
107 | return err
108 | }
109 | if err := g.SetKeybinding("tl", gocui.KeyArrowLeft, gocui.ModNone, backtoSearch); err != nil {
110 | return err
111 | }
112 | if err := g.SetKeybinding("tl", gocui.KeyEnter, gocui.ModNone, selectit); err != nil {
113 | return err
114 | }
115 | if err := g.SetKeybinding("tl", gocui.KeyArrowRight, gocui.ModNone, selectit); err != nil {
116 | return err
117 | }
118 | if err := g.SetKeybinding("tl", 'q', gocui.ModNone, quit); err != nil {
119 | return err
120 | }
121 | if err := g.SetKeybinding("tl", 'a', gocui.ModNone, sortAsc); err != nil {
122 | return err
123 | }
124 | if err := g.SetKeybinding("tl", 'd', gocui.ModNone, sortDec); err != nil {
125 | return err
126 | }
127 | var cv *gocui.View
128 | if cv, err = g.SetCurrentView("tl"); err != nil {
129 | return err
130 | }
131 |
132 | if len(torrents) != 0 {
133 | maxlentl := int(float64(maxX) / 1.35)
134 | tw := tabwriter.NewWriter(tllist, 0, 0, 1, ' ', 0)
135 | t := tabby.NewCustom(tw)
136 | t.AddLine(term_res+"#", term_res+"Name", term_res+"Size", term_res+"Seeds", term_res+"Leeches"+term_res)
137 | for idno, eachtorrent := range torrents {
138 | t.AddLine(term_yell+strconv.Itoa(idno+1),
139 | term_cyan+maxstring(eachtorrent.Text, maxlentl),
140 | term_purp+fmt.Sprintf("%f", 0.000000001*eachtorrent.Length)+" GB",
141 | term_green+"S:"+strconv.Itoa(eachtorrent.Seeds),
142 | term_red+"L:"+strconv.Itoa(eachtorrent.Leechs)+term_res)
143 | }
144 | t.Print()
145 | }
146 |
147 | for mc := 0; mc <= selid+1; mc++ {
148 | if ccerr := cv.SetCursor(0, mc); ccerr != nil {
149 | ox, oy := cv.Origin()
150 | if soerr := cv.SetOrigin(ox, oy+1); soerr != nil {
151 | return soerr
152 | }
153 | }
154 | }
155 | }
156 |
157 | if tlhelp, err := g.SetView("tlhelp", -1, maxY-2, maxX, maxY); err != nil {
158 | if err != gocui.ErrUnknownView {
159 | return err
160 | }
161 | tlhelp.Frame = false
162 | tlhelp.Wrap = true
163 | fmt.Fprintf(tlhelp, "use ↑ ↓ to navigate, or → to select , ← to go back to search , (a) to Ascending-Sort , (d) to Descending-Sort and (q) or Ctrl-c to quit")
164 |
165 | }
166 |
167 | return nil
168 | }
169 |
--------------------------------------------------------------------------------
/searchui.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 |
7 | "github.com/jroimartin/gocui"
8 | )
9 |
10 | func searchui(g *gocui.Gui) error {
11 |
12 | maxX, maxY := g.Size()
13 |
14 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
15 | log.Println(err)
16 | }
17 |
18 | // Header
19 | if header, err := g.SetView("header", maxX/2-9, maxY/2-6, maxX/2+9, maxY/2-3); err != nil {
20 | if err != gocui.ErrUnknownView {
21 | return err
22 | }
23 | header.Frame = false
24 | fmt.Fprintln(header, " Search the")
25 | fmt.Fprintln(header, term_yell, "Torrent Paradise", term_res)
26 | }
27 |
28 | // Input Box
29 | if editbox, err := g.SetView("inputbox", maxX/2-17, maxY/2-2, maxX/2+17, maxY/2); err != nil {
30 | if err != gocui.ErrUnknownView {
31 | return err
32 | }
33 | editbox.Editable = true
34 |
35 | if err := g.SetKeybinding("inputbox", gocui.KeyEnter, gocui.ModNone, doSearch); err != nil {
36 | return err
37 | }
38 |
39 | if err := g.SetKeybinding("inputbox", gocui.KeyArrowRight, gocui.ModNone, doSearch); err != nil {
40 | return err
41 | }
42 |
43 | if err := g.SetKeybinding("inputbox", gocui.KeyHome, gocui.ModNone, doAbout); err != nil {
44 | return err
45 | }
46 |
47 | if err := g.SetKeybinding("inputbox", gocui.KeyDelete, gocui.ModNone, resetInput); err != nil {
48 | return err
49 | }
50 | if err := g.SetKeybinding("inputbox", gocui.KeyArrowLeft, gocui.ModNone, quit); err != nil {
51 | return err
52 | }
53 | if _, err := g.SetCurrentView("inputbox"); err != nil {
54 | return err
55 | }
56 | }
57 |
58 | // Enter to Search View
59 | if srview, err := g.SetView("srview", maxX/2-9, maxY/2+1, maxX/2+9, maxY/2+3); err != nil {
60 | if err != gocui.ErrUnknownView {
61 | return err
62 | }
63 | srview.Frame = false
64 | fmt.Fprintln(srview, term_grey, "Enter to Search", term_res)
65 | }
66 | // Help Widget
67 | if srhelp, err := g.SetView("srhelp", -1, maxY-2, maxX, maxY); err != nil {
68 | if err != gocui.ErrUnknownView {
69 | return err
70 | }
71 | srhelp.Frame = true
72 | srhelp.Wrap = true
73 | fmt.Fprintln(srhelp, "Press / → to Search , ← / Ctrl-c to Quit , for About Menu")
74 |
75 | }
76 |
77 | return nil
78 | }
79 |
80 | func resetInput(g *gocui.Gui, v *gocui.View) error {
81 | inpbox, _ := g.View("inputbox")
82 | inpbox.Clear()
83 | inpbox.SetCursor(0, 0)
84 | inpbox.SetOrigin(0, 0)
85 | return nil
86 | }
87 |
--------------------------------------------------------------------------------
/selectui.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | "strings"
7 | "text/tabwriter"
8 |
9 | "github.com/atotto/clipboard"
10 | "github.com/cheynewallace/tabby"
11 | "github.com/jroimartin/gocui"
12 | "github.com/skratchdot/open-golang/open"
13 | )
14 |
15 | func selectfunc(g *gocui.Gui) error {
16 |
17 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
18 | fmt.Println(err)
19 | }
20 |
21 | maxX, maxY := g.Size()
22 |
23 | // Select Widget
24 | if selwid, err := g.SetView("selwid", -1, -1, maxX, maxY-2); err != nil {
25 | if err != gocui.ErrUnknownView {
26 | return err
27 | }
28 |
29 | selwid.Wrap = true
30 |
31 | if err := g.SetKeybinding("selwid", gocui.KeySpace, gocui.ModNone, backtoSearch); err != nil {
32 | return err
33 | }
34 | if err := g.SetKeybinding("selwid", gocui.KeyEnter, gocui.ModNone, openmagnet); err != nil {
35 | return err
36 | }
37 | if err := g.SetKeybinding("selwid", gocui.KeyArrowRight, gocui.ModNone, openmagnet); err != nil {
38 | return err
39 | }
40 | if err := g.SetKeybinding("selwid", gocui.KeyArrowLeft, gocui.ModNone, backtoTorlist); err != nil {
41 | return err
42 | }
43 | if err := g.SetKeybinding("selwid", gocui.KeySpace, gocui.ModNone, backtoSearch); err != nil {
44 | return err
45 | }
46 | if err := g.SetKeybinding("selwid", 'q', gocui.ModNone, quit); err != nil {
47 | return err
48 | }
49 | if _, err := g.SetCurrentView("selwid"); err != nil {
50 | return err
51 | }
52 |
53 | selwid.Frame = true
54 | selwid.Wrap = true
55 | selwid.MoveCursor(maxX-1, 0, true)
56 |
57 | magnet := magnetheader + torrents[selid].Id + trackerurl
58 |
59 | fmt.Fprintln(selwid, term_yell+"Torrent Details for Torrent ", selid+1, " →"+term_res)
60 | tw := tabwriter.NewWriter(selwid, 0, 0, 2, ' ', 0)
61 | t := tabby.NewCustom(tw)
62 | t.AddLine(term_res+"\u2022 Name"+term_cyan, torrents[selid].Text)
63 | t.AddLine(term_res+"\u2022 Size"+term_purp, torrents[selid].Length)
64 | t.AddLine(term_res+"\u2022 Leechs"+term_red, torrents[selid].Leechs)
65 | t.AddLine(term_res+"\u2022 Infohash"+term_cyan, torrents[selid].Leechs)
66 | t.AddLine(term_res+"\u2022 Magnet"+term_green, magnetheader+torrents[selid].Id)
67 | t.AddLine(term_res+"\u2022 Trackerlist URL"+term_purp, trackerlisturl)
68 | t.AddLine(term_res, term_res)
69 | t.Print()
70 | fmt.Fprintln(selwid, term_cyan, "Press to Open this Magnet link in Preconfigured Application\n", term_res)
71 |
72 | // Write to clipboard
73 | cliperr := clipboard.WriteAll(magnet)
74 | if cliperr != nil {
75 | fmt.Fprintln(selwid, term_red, "Error copying Magnet link :", cliperr.Error(), term_res)
76 | } else {
77 | fmt.Fprintln(selwid, term_green, "Magnet Link with added trackers has been copied to your device's clipboard\n\n", term_res)
78 | }
79 |
80 | }
81 |
82 | if selhelp, err := g.SetView("selhelp", -1, maxY-2, maxX, maxY); err != nil {
83 |
84 | if err != gocui.ErrUnknownView {
85 | return err
86 | }
87 |
88 | selhelp.Frame = false
89 | selhelp.Wrap = true
90 | fmt.Fprintf(selhelp, "Press or → to Open , ← to go back , to go to Search, (q) or Ctrl-c to quit")
91 |
92 | }
93 |
94 | return nil
95 | }
96 |
97 | func openmagnet(g *gocui.Gui, v *gocui.View) error {
98 | err := open.Run(magnetheader + torrents[selid].Id + trackerurl)
99 | selwid, _ := g.View("selwid")
100 | if err != nil {
101 | fmt.Fprintln(selwid, term_red, "Error opening the magnet: ", err.Error(), term_res)
102 | return nil
103 | } else {
104 | fmt.Fprintln(selwid, term_green, "Torrent has been opened in the configured Application")
105 | }
106 | return nil
107 | }
108 |
109 | func selectit(g *gocui.Gui, v *gocui.View) (err error) {
110 | _, cy := v.Cursor()
111 | selline, err := v.Line(cy)
112 | selArr := strings.Fields(selline)
113 | if len(selArr) > 0 {
114 | if selArr[0] == "#" {
115 | return nil
116 | }
117 | selid, err = strconv.Atoi(selArr[0])
118 | if err != nil {
119 | return nil
120 | }
121 | selid = selid - 1
122 |
123 | } else {
124 | return nil
125 | }
126 | if len(datafilename) > 0 {
127 | switch datatype {
128 | case 4, 5:
129 | g.SetManagerFunc(torrentdatawrite)
130 | return nil
131 | default:
132 | g.SetManagerFunc(selectfunc)
133 | return nil
134 |
135 | }
136 | }
137 | g.SetManagerFunc(selectfunc)
138 |
139 | return nil
140 | }
141 |
--------------------------------------------------------------------------------
/torpar.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/jroimartin/gocui"
8 | )
9 |
10 | func main() {
11 | // Generate tracker part of Magnet URL in another goroutine
12 | go gentrackers()
13 |
14 | // Start our TUI - new gocui instance
15 | tui, err := gocui.NewGui(gocui.OutputNormal)
16 | if err != nil {
17 | fmt.Println(err)
18 | os.Exit(1)
19 | }
20 | defer tui.Close()
21 |
22 | tui.Cursor = true // Enable cursor
23 | if len(query) > 0 {
24 | if err := torparadise(tui); err != nil {
25 | fmt.Println(err)
26 | os.Exit(1)
27 | }
28 | } else {
29 | tui.SetManagerFunc(searchui) // Layout is handled by Mainlayout function
30 | }
31 |
32 | if err := tui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
33 | fmt.Println(err)
34 | os.Exit(1)
35 | }
36 |
37 | if err := tui.MainLoop(); err != nil && err != gocui.ErrQuit {
38 | fmt.Println(err)
39 | os.Exit(1)
40 | }
41 | tui.Close()
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/torrent.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "io/ioutil"
6 | "net/http"
7 | "net/url"
8 | "strings"
9 | )
10 |
11 | // Fetch List of Best Public Torrent Trackers Available and create trackerlist out of it.
12 | func gentrackers() {
13 | // Different URLs and options to fetch trackers from
14 | if trackerquery != "" {
15 | switch trackerquery {
16 | case "tl":
17 | trackerlisturl = "https://ngosang.github.io/trackerslist/trackers_all.txt"
18 | break
19 | case "nt":
20 | trackerlisturl = "https://newtrackon.com/api/stable"
21 | default:
22 | trackerlisturl = trackerquery
23 | }
24 | } else {
25 | trackerlisturl = "https://newtrackon.com/api/stable"
26 | }
27 | if trackerurl != "" {
28 | return
29 | }
30 |
31 | trackersresponse, err := http.Get(trackerlisturl)
32 | if err != nil {
33 | return
34 | }
35 | defer trackersresponse.Body.Close()
36 |
37 | trackers, err := ioutil.ReadAll(trackersresponse.Body)
38 | if err != nil {
39 | return
40 | }
41 | var trackerlist strings.Builder
42 | scanner := bufio.NewScanner(strings.NewReader(string(trackers)))
43 | for scanner.Scan() {
44 | trackerlist.WriteString("&tr=" + url.QueryEscape(scanner.Text()))
45 | }
46 | trackerurl = trackerlist.String()
47 | waitforme.Done()
48 | }
49 |
--------------------------------------------------------------------------------