Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jackbillstrom committed Aug 8, 2023
0 parents commit 2ad001b
Show file tree
Hide file tree
Showing 13 changed files with 445 additions and 0 deletions.
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# tpb-tui

![alt](screenshot.png)

`tpb-tui` is a terminal user interface for The Pirate Bay. It lets you search for torrents and display them in a table. Selecting a torrent will open the magnet link in your default torrent client.

## Features

- Search for torrents directly from your terminal.
- Display torrents in a neat table format.
- Open magnet links in the default torrent client.
- User-friendly controls: Enter to select, Esc to go back or exit.

## Installation

Make sure you have Go installed (v1.20 or newer is recommended).

To install `tpb-tui`, run:

```bash
go get -u github.com/jackbillstrom/tpb-tui
```

## Usage

After installation, run the application using:

```bash
tpb-tui
```

Follow the on-screen prompts to search for torrents and navigate through the results.

## Contributing

If you'd like to contribute, please fork the repository and make changes as you'd like. Pull requests are warmly welcome.

1. Fork the Project
2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
3. Commit your Changes (git commit -m 'Add some AmazingFeature')
4. Push to the Branch (git push origin feature/AmazingFeature)
5. Open a Pull Request

## License

This project is licensed under the MIT License - see the LICENSE file for details.

## Dependencies

tpb-tui relies on several third-party Go packages. Here are some of the major dependencies:

[bubbletea/examples/table/main.go](https://github.com/charmbracelet/bubbletea/blob/master/examples/table/main.go) for the table UI.

[github.com/charmbracelet/bubbles](github.com/charmbracelet/bubbles) for UI components.

[github.com/charmbracelet/bubbletea](github.com/charmbracelet/bubbletea) for the app's model-view-update architecture.

[github.com/charmbracelet/lipgloss](github.com/charmbracelet/lipgloss) for styling.

## Acknowledgements

A special thanks to the developers of all the Go packages used in this project.
28 changes: 28 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module tpb-tui

go 1.20

require (
github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.24.2
)

require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/lipgloss v0.7.1
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.3.8 // indirect
)
41 changes: 41 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
6 changes: 6 additions & 0 deletions internal/models/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package models

type (
ErrMsg error
ViewState int
)
17 changes: 17 additions & 0 deletions internal/models/torrent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package models

// Torrent is a struct that represents a torrent from the TPB API
type Torrent struct {
ID string `json:"id"`
Name string `json:"name"`
InfoHash string `json:"info_hash"`
Leechers string `json:"leechers"`
Seeders string `json:"seeders"`
NumFiles string `json:"num_files"`
Size string `json:"size"`
Username string `json:"username"`
Added string `json:"added"`
Status string `json:"status"`
Category string `json:"category"`
Imdb string `json:"imdb"`
}
39 changes: 39 additions & 0 deletions internal/ui/ui.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package ui

import (
"tpb-tui/internal/models"

"github.com/charmbracelet/bubbles/table"
)

// TorrentsToTableModel converts a slice of Torrents to a table.Model
func TorrentsToTableModel(torrents []models.Torrent) table.Model {
// Define the table columns
columns := []table.Column{
{Title: "Name", Width: 30},
{Title: "Seeders", Width: 10},
{Title: "Leechers", Width: 10},
{Title: "Size", Width: 20},
{Title: "Username", Width: 20},
}

rows := make([]table.Row, len(torrents))

for i, t := range torrents {
row := table.Row{
t.Name,
t.Seeders,
t.Leechers,
t.Size,
t.Username,
}
rows[i] = row
}

// Create the table
return table.New(
table.WithColumns(columns),
table.WithRows(rows),
table.WithFocused(true),
)
}
148 changes: 148 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package main

/*
* go-tpb-tui is a terminal user interface for The Pirate Bay
* It uses their public API to search for torrents and display them in a table
* Pressing enter on a torrent will open the magnet link in your default torrent client (if you have one set up)
* Pressing Ctrl+C will exit the application
* Pressing Esc will go back to the search input (if you're in the table view)
*/

import (
"fmt"
"log"
"strings"
"tpb-tui/internal/models"
"tpb-tui/internal/ui"
"tpb-tui/pkg/tpb"
"tpb-tui/pkg/utils"

"github.com/charmbracelet/bubbles/table"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)

// baseStyle is the base style for the application (Snatched from: https://github.com/charmbracelet/bubbletea/blob/master/examples/table/main.go)
var baseStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("240")).
Padding(1, 1)

const (
TextInputView models.ViewState = iota
TableView
)

// AppState is the application state
type AppState struct {
ViewState models.ViewState
Table table.Model
TextInput textinput.Model
Torrents []models.Torrent
Err error
ErrMessage string
}

func main() {
p := tea.NewProgram(initialModel())
if _, err := p.Run(); err != nil {
log.Fatal(err)
}
}

// initialModel returns the initial application state
func initialModel() AppState {
// Set up the search input field
ti := textinput.New()
ti.Placeholder = "The Game 1997"
ti.Focus()
ti.CharLimit = 156
ti.Width = 20

return AppState{
TextInput: ti,
Err: nil,
}
}

// Init is called when the application starts
func (m AppState) Init() tea.Cmd {
return textinput.Blink
}

// Update handles the interaction with the application (key presses, etc.)
func (m AppState) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd

switch msg := msg.(type) {
case tea.KeyMsg:
if msg.Type == tea.KeyCtrlC {
return m, tea.Quit
}

switch m.ViewState {
// ? Textinput visas (default)
case TextInputView:
switch msg.Type {
case tea.KeyEnter:
query := m.TextInput.Value()
if strings.TrimSpace(query) == "" {
m.ErrMessage = "Please enter a valid search query."
return m, nil
}
return m, tpb.SearchTorrents(query)
case tea.KeyEsc:
return m, tea.Quit
}
m.TextInput, cmd = m.TextInput.Update(msg)
return m, cmd

// ? Tabellen visas
case TableView:
switch msg.Type {
case tea.KeyEnter:
// Get the selected torrent
selectedIndex := m.Table.Cursor()
if selectedIndex < len(m.Torrents) {
selectedTorrent := m.Torrents[selectedIndex]
magnetLink := utils.GenerateMagnetLink(selectedTorrent)
utils.OpenURL(magnetLink)
}
case tea.KeyEsc:
m.ViewState = TextInputView
default:
m.Table, cmd = m.Table.Update(msg)
}
return m, cmd
}

case models.ErrMsg:
m.Err = msg
return m, nil

case []models.Torrent:
m.Torrents = msg
m.Table = ui.TorrentsToTableModel(m.Torrents)
m.ViewState = TableView
return m, nil
}

return m, nil
}

// View renders the application
func (m AppState) View() string {
switch m.ViewState {
case TextInputView:
return fmt.Sprintf(
"Which torrent are you looking for? 🕵\n\n%s\n\n%s\n(esc to quit)",
m.TextInput.View(),
m.ErrMessage,
) + "\n"
case TableView:
return baseStyle.Render(m.Table.View()) + "\n\n(Enter to open magnetlink) (esc to go back) (ctrl+c to quit)\n"
default:
return "Unknown view state!"
}
}
28 changes: 28 additions & 0 deletions pkg/tpb/tpb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package tpb

import (
"encoding/json"
"net/http"
"net/url"
"tpb-tui/internal/models"

tea "github.com/charmbracelet/bubbletea"
)

// SearchTorrents searches from the TPB API
func SearchTorrents(query string) tea.Cmd {
return func() tea.Msg {
endpoint := "https://apibay.org/q.php"
resp, err := http.Get(endpoint + "?q=" + url.QueryEscape(query) + "&cat=")
if err != nil {
return models.ErrMsg(err)
}
defer resp.Body.Close()

var torrents []models.Torrent
if err := json.NewDecoder(resp.Body).Decode(&torrents); err != nil {
return models.ErrMsg(err)
}
return torrents
}
}
Loading

0 comments on commit 2ad001b

Please sign in to comment.