├── Procfile
├── test-go
├── go.mod
├── test.torrent
├── go.sum
├── main.go
└── index.html
├── .gitignore
├── static
├── img
│ └── Screenshot_2022-05-23-12-00-44.png
├── js
│ ├── player.js
│ ├── addons.js
│ ├── search.js
│ ├── dark.js
│ ├── torr.js
│ └── main.js
├── player.html
├── css
│ └── style.css
├── downloads.html
├── search.html
└── index.html
├── .vscode
└── launch.json
├── .github
└── FUNDING.yml
├── main.go
├── go.mod
├── README.md
├── torrent.go
├── helpers.go
├── handlers.go
├── assets
├── test.html
└── js
│ └── app.js
└── go.sum
/Procfile:
--------------------------------------------------------------------------------
1 | web: bin/main
2 |
--------------------------------------------------------------------------------
/test-go/go.mod:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | go 1.22.3
4 |
5 | require github.com/jackpal/bencode-go v1.0.2
6 |
--------------------------------------------------------------------------------
/test-go/test.torrent:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmarnathCJD/CloudTorrent/HEAD/test-go/test.torrent
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | main
2 | downloads/downloads/torrents/torrents.db
3 | downloads/downloads/torrents.db
4 | downloads/torrents.db
5 | s.sh
--------------------------------------------------------------------------------
/static/img/Screenshot_2022-05-23-12-00-44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmarnathCJD/CloudTorrent/HEAD/static/img/Screenshot_2022-05-23-12-00-44.png
--------------------------------------------------------------------------------
/test-go/go.sum:
--------------------------------------------------------------------------------
1 | github.com/jackpal/bencode-go v1.0.2 h1:LcCNfZ344u0LpBPOZNjpCLps/wUOuN4r87Fy9+5yU8g=
2 | github.com/jackpal/bencode-go v1.0.2/go.mod h1:6jI9mUjO3GQbZti3JizEfxTzRfWOM8oBBcwbwlTfceI=
3 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [],
7 | "go.alternateTools": {
8 | "go": "/snap/go/current/bin/go"
9 | },
10 | }
--------------------------------------------------------------------------------
/static/js/player.js:
--------------------------------------------------------------------------------
1 | var src = window.location.pathname.replace("/stream", "");
2 | var player_div = document.getElementById("player-div");
3 |
4 | player_div.innerHTML =
5 | '';
8 |
9 | const player = new Plyr("video", {
10 | controls: [
11 | "play-large",
12 | "play",
13 | "progress",
14 | "current-time",
15 | "mute",
16 | "volume",
17 | "settings",
18 | "fullscreen",
19 | "download",
20 | ],
21 | title: src.split("/").pop().split(".")[0],
22 | ratio: "16:9",
23 | clickToPlay: true,
24 | settings: ["captions", "quality", "speed"],
25 | });
26 |
27 | window.player = player;
28 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [xamarnath]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] t
14 |
--------------------------------------------------------------------------------
/test-go/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io/ioutil"
7 | "log"
8 |
9 | bencode "github.com/jackpal/bencode-go"
10 | )
11 |
12 | type Torrent struct {
13 | Announce string
14 | Info string
15 | }
16 |
17 | func decodeTorrent(data []byte) (map[string]interface{}, error) {
18 | var torrent map[string]interface{}
19 |
20 | reader := bytes.NewReader(data)
21 | err := bencode.Unmarshal(reader, &torrent)
22 | if err != nil {
23 | return nil, err
24 | }
25 | return torrent, nil
26 | }
27 |
28 | func main() {
29 | file := "test.torrent"
30 |
31 | data, err := ioutil.ReadFile(file)
32 |
33 | if err != nil {
34 | log.Fatal(err)
35 | }
36 |
37 | torrent, err := decodeTorrent(data)
38 |
39 | if err != nil {
40 | log.Fatal(err)
41 | }
42 |
43 | fmt.Println(torrent)
44 | }
45 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "html/template"
6 | "log"
7 | "net/http"
8 | "os"
9 | "path/filepath"
10 | )
11 |
12 | var (
13 | Wd, _ = os.Getwd()
14 | Root = filepath.Join(Wd, "downloads")
15 | Port = GetOutboundPort()
16 | )
17 |
18 | func main() {
19 | fmt.Print("Starting server...")
20 | HTMLServe()
21 | go streamTorrentUpdate()
22 | if err := http.ListenAndServe(Port, nil); err != nil {
23 | log.Fatal("ListenAndServe: ", err)
24 | }
25 | }
26 |
27 | func HTMLServe() {
28 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
29 | http.ServeFile(w, r, "./static/index.html")
30 | })
31 | http.HandleFunc("/downloads/", func(w http.ResponseWriter, r *http.Request) {
32 | template := template.Must(template.ParseFiles("./static/downloads.html"))
33 | template.Execute(w, nil)
34 | })
35 | http.HandleFunc("/stream/", func(w http.ResponseWriter, r *http.Request) {
36 | template := template.Must(template.ParseFiles("./static/player.html"))
37 | template.Execute(w, nil)
38 | })
39 | http.HandleFunc("/search/", func(w http.ResponseWriter, r *http.Request) {
40 | template := template.Must(template.ParseFiles("./static/search.html"))
41 | template.Execute(w, nil)
42 | })
43 | // static files
44 | http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
45 | }
46 |
47 | func init() {
48 | log.SetFlags(log.LstdFlags | log.Lshortfile)
49 | http.HandleFunc("/test/", func(w http.ResponseWriter, r *http.Request) {
50 | template := template.Must(template.ParseFiles("./assets/test.html"))
51 | template.Execute(w, nil)
52 | })
53 | http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("./assets"))))
54 | }
55 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | go 1.17
4 |
5 | require github.com/shirou/gopsutil v3.21.11+incompatible
6 |
7 | require (
8 | github.com/cenkalti/backoff/v3 v3.2.2 // indirect
9 | github.com/cenkalti/log v1.0.0 // indirect
10 | github.com/gofrs/uuid v4.2.0+incompatible // indirect
11 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
12 | github.com/google/btree v1.0.1 // indirect
13 | github.com/hashicorp/errwrap v1.1.0 // indirect
14 | github.com/hashicorp/go-multierror v1.1.1 // indirect
15 | github.com/jackpal/bencode-go v1.0.0 // indirect
16 | github.com/juju/ratelimit v1.0.1 // indirect
17 | github.com/klauspost/cpuid/v2 v2.0.12 // indirect
18 | github.com/kr/pretty v0.3.0 // indirect
19 | github.com/mattn/go-isatty v0.0.14 // indirect
20 | github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
21 | github.com/minio/sha256-simd v1.0.0 // indirect
22 | github.com/mitchellh/go-homedir v1.1.0 // indirect
23 | github.com/mr-tron/base58 v1.2.0 // indirect
24 | github.com/multiformats/go-multihash v0.1.0 // indirect
25 | github.com/multiformats/go-varint v0.0.6 // indirect
26 | github.com/nictuku/dht v0.0.0-20201226073453-fd1c1dd3d66a // indirect
27 | github.com/nictuku/nettools v0.0.0-20150117095333-8867a2107ad3 // indirect
28 | github.com/powerman/rpc-codec v1.2.2 // indirect
29 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
30 | github.com/rogpeppe/go-internal v1.8.1 // indirect
31 | github.com/sirupsen/logrus v1.8.1 // indirect
32 | github.com/spaolacci/murmur3 v1.1.0 // indirect
33 | github.com/youtube/vitess v3.0.0-rc.3+incompatible // indirect
34 | github.com/zeebo/bencode v1.0.0 // indirect
35 | go.etcd.io/bbolt v1.3.6 // indirect
36 | golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 // indirect
37 | golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect
38 | lukechampine.com/blake3 v1.1.7 // indirect
39 | )
40 |
41 | require (
42 | github.com/cenkalti/rain v1.8.8
43 | github.com/go-ole/go-ole v1.2.6 // indirect
44 | github.com/julienschmidt/sse v0.0.0-20190921213156-72db694fe9e6
45 | github.com/yusufpapurcu/wmi v1.2.2 // indirect
46 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
47 | )
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ~ New In Development Version ~
3 |
4 |
5 |
6 | **Cloud torrent** is a a self-hosted remote torrent client, written in Go (golang). You start torrents remotely, which are downloaded as sets of files on the local disk of the server, which are then retrievable or streamable via HTTP.
7 |
8 | ### Features
9 |
10 | - Single binary
11 | - Cross platform
12 | - Embedded torrent search
13 | - Real-time updates
14 | - Mobile-friendly
15 | - Fast [content server](http://golang.org/pkg/net/http/#ServeContent)
16 |
17 | See [Future Features here](#future-features)
18 |
19 | ### Install
20 |
21 | **Binaries**
22 |
23 | [](https://github.com/jpillora/cloud-torrent/releases) [](https://github.com/jpillora/cloud-torrent/releases)
24 |
25 | See [the latest release](https://github.com/jpillora/cloud-torrent/releases/latest) or download and install it now with
26 |
27 | ```
28 | git clone https://github.com/xamarnath/CloudTorrent && cd CloudTorrent && go build . && ./main
29 | ```
30 |
31 | **Source**
32 |
33 | _[Go](https://golang.org/dl/) is required to install from source_
34 |
35 | ```sh
36 | $ go get -v github.com/jpillora/cloud-torrent
37 | ```
38 |
39 | ### Usage
40 |
41 | ```
42 | $ https://github.com/xamarnath/CloudTorrent
43 | $ go build
44 | $ ./main
45 | ```
46 |
47 | ### Future features
48 |
49 | In summary, the core features will be:
50 | TODO
51 |
52 | - **File Transforms**
53 |
54 | During a file tranfer, one could apply different transforms against the byte stream for various effect. For example, supported transforms might include: video transcoding (using ffmpeg), encryption and decryption, [media sorting](https://github.com/jpillora/cloud-torrent/issues/4) (file renaming), and writing multiple files as a single zip file.
55 |
56 | - **Automatic updates** Binary will upgrade itself, adding new features as they get released.
57 | - **RSS** Automatically add torrents, with smart episode filter.
58 |
59 | Once completed, cloud-torrent will no longer be a simple torrent client and most likely project be renamed.
60 |
61 | Copyright (c) 2022 RoseLoverX
62 |
--------------------------------------------------------------------------------
/static/player.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
42 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/static/js/addons.js:
--------------------------------------------------------------------------------
1 | var NumToasts = 0;
2 |
3 | function ToastMessage(message, bg) {
4 | NumToasts++;
5 | document.querySelector(".toast-container").innerHTML +=
6 | `` +
10 | message +
11 | `
`;
12 | var Toast = bootstrap.Toast.getOrCreateInstance(
13 | document.getElementById("toast-main-" + NumToasts)
14 | );
15 | Toast.options = {
16 | delay: 5000,
17 | autohide: true,
18 | };
19 | Toast.show();
20 | var PrevToast = document.getElementById("toast-main-" + (NumToasts - 1));
21 | if (PrevToast) {
22 | PrevToast.remove();
23 | }
24 | }
25 |
26 | function copyToClipboard(e) {
27 | var copyText = $(e).data("url");
28 | navigator.clipboard.writeText(copyText).then(
29 | function () {
30 | ToastMessage("Copied to clipboard", "success");
31 | },
32 | function () {
33 | ToastMessage("Failed to copy to clipboard", "error");
34 | }
35 | );
36 | }
37 |
38 | function zipDir(e) {
39 | var path = e.getAttribute("data-path");
40 | $.ajax({
41 | url: "/api/zip/" + path,
42 | type: "GET",
43 | dataType: "json",
44 | success: function (data) {
45 | ToastMessage("Zipped Directory", "success");
46 | e.outerHTML =
47 | ``;
50 | },
51 | });
52 | }
53 |
54 | function btnHref(e) {
55 | var path = $(e).data("path");
56 | window.location.href = path;
57 | }
58 |
59 | function ToClipboard(id) {
60 | elem = document.getElementById("btn-" + id);
61 | data = elem.getAttribute("data-clipboard-text");
62 | navigator.clipboard.writeText(data).then(
63 | function () {
64 | ToastMessage("Copied to clipboard", "success");
65 | },
66 | function () {
67 | ToastMessage("Failed to copy to clipboard", "error");
68 | }
69 | );
70 | }
71 |
--------------------------------------------------------------------------------
/static/css/style.css:
--------------------------------------------------------------------------------
1 | @import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css");
2 | @import url("https://cdn.jsdelivr.net/npm/bootstrap@5/dist/css/bootstrap.min.css");
3 |
4 | [data-theme="dark"] {
5 | background-color: #111 !important;
6 | color: #eee;
7 | }
8 |
9 | [data-theme="dark"] .bg-black {
10 | background-color: #fff !important;
11 | }
12 |
13 | [data-theme="dark"] .bg-dark {
14 | background-color: #eee !important;
15 | }
16 |
17 | [data-theme="dark"] .bg-light {
18 | background-color: #222 !important;
19 | }
20 |
21 | [data-theme="dark"] .bg-white {
22 | background-color: #000 !important;
23 | }
24 |
25 | .modal {
26 | display: none;
27 | position: fixed;
28 | z-index: 1;
29 | padding-top: 100px;
30 | left: 0;
31 | top: 0;
32 | width: 100%;
33 | height: 100%;
34 | overflow: auto;
35 | background-color: rgb(0, 0, 0);
36 | background-color: rgba(0, 0, 0, 0.9);
37 | }
38 |
39 | .modal-content {
40 | margin: auto;
41 | display: block;
42 | width: 80%;
43 | max-width: 700px;
44 | }
45 |
46 | .caption {
47 | margin: auto;
48 | display: block;
49 | width: 80%;
50 | max-width: 700px;
51 | text-align: center;
52 | color: #ccc;
53 | padding: 10px 0;
54 | height: 150px;
55 | }
56 |
57 | .modal-content,
58 | #caption {
59 | -webkit-animation-name: zoom;
60 | -webkit-animation-duration: 0.6s;
61 | animation-name: zoom;
62 | animation-duration: 0.6s;
63 | }
64 |
65 | @-webkit-keyframes zoom {
66 | from {
67 | -webkit-transform: scale(0);
68 | }
69 |
70 | to {
71 | -webkit-transform: scale(1);
72 | }
73 | }
74 |
75 | @keyframes zoom {
76 | from {
77 | transform: scale(0);
78 | }
79 |
80 | to {
81 | transform: scale(1);
82 | }
83 | }
84 |
85 | .close {
86 | position: absolute;
87 | top: 15px;
88 | right: 35px;
89 | color: #f1f1f1;
90 | font-size: 40px;
91 | font-weight: bold;
92 | transition: 0.3s;
93 | }
94 |
95 | .close:hover,
96 | .close:focus {
97 | color: #bbb;
98 | text-decoration: none;
99 | cursor: pointer;
100 | }
101 |
102 | html {
103 | --plyr-color-main: #f18f35;
104 | --plyr-color-main-hover: #c26917;
105 | }
106 |
107 | .player-container {
108 | max-width: 750px;
109 | }
110 |
111 | @media only screen and (max-width: 768px) {
112 | .modal-content {
113 | width: 100%;
114 | }
115 |
116 | .player-container {
117 | max-width: 100%;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/static/downloads.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
40 |
41 |
42 |
45 |
46 |
×
47 |
![]()
48 |
49 |
50 |
51 |
52 |
55 |
56 |
57 |
58 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/static/js/search.js:
--------------------------------------------------------------------------------
1 | var Results = document.getElementById("results");
2 | var searchBox = document.getElementById("search");
3 | var Button = document.getElementById("searchBtn");
4 |
5 | function SearchTorrents() {
6 | var Query = searchBox.value;
7 | if (Query == "") {
8 | Query = "top100";
9 | }
10 | var url = `/api/search?q=` + Query;
11 | $.ajax({
12 | url: url,
13 | type: "GET",
14 | dataType: "json",
15 | success: function (data) {
16 | Results.innerHTML = "";
17 | var searchCount = document.getElementById("search-count");
18 | searchCount.innerHTML = `(${data.length} results)`;
19 | for (var i = 0; i < data.length; i++) {
20 | var file = data[i];
21 | file.magnet = file.magnet.replace(/\s/g, "+");
22 | var a = ``;
23 | if (IsDark()) {
24 | a = ``;
25 | }
26 | a += ``;
27 | a +=
28 | `
1. ` +
29 | file.name +
30 | `
`;
31 | a += `` + file.size + ``;
32 | a += `
`;
33 | a += `Seeds: `;
34 | if (file.seeders > 2000) {
35 | a += `` + file.seeders + ``;
36 | } else if (file.seeders > 1000) {
37 | a += `` + file.seeders + ``;
38 | } else if (file.seeders > 500) {
39 | a += `` + file.seeders + ``;
40 | } else if (file.seeders < 5) {
41 | a += `` + file.seeders + ``;
42 | a += ``;
43 | } else {
44 | a += `` + file.seeders + ``;
45 | }
46 | a += `, Leeches: ` + file.leechers + `
`;
47 | a += ``;
48 | a += `
`;
49 | a += ``;
50 | a += ``;
51 | a += `
`;
52 | a += ``;
53 | Results.innerHTML += a;
54 | }
55 | },
56 | });
57 | }
58 |
59 | SearchTorrents();
60 |
61 | Button.addEventListener("click", SearchTorrents);
62 |
63 | function addTorrent(magnet) {
64 | $.ajax({
65 | url: "/api/add",
66 | type: "POST",
67 | data: {
68 | magnet: magnet,
69 | },
70 | success: function (data) {
71 | ToastMessage("Torrent added successfully", "success");
72 | },
73 | error: function (data) {
74 | if (data.status == 500) {
75 | ToastMessage("Torrent already added", "warning");
76 | }
77 | },
78 | });
79 | }
80 |
--------------------------------------------------------------------------------
/static/js/dark.js:
--------------------------------------------------------------------------------
1 | var darkSwitch = document.getElementById("darkSwitch");
2 | var DarkIcon = document.getElementById("dark-icon");
3 |
4 | window.addEventListener("load", function () {
5 | if (darkSwitch) {
6 | initTheme();
7 | darkSwitch.addEventListener("change", function () {
8 | resetTheme();
9 | });
10 | }
11 | });
12 |
13 | function initTheme() {
14 | var darkThemeSelected =
15 | localStorage.getItem("darkSwitch") !== null &&
16 | localStorage.getItem("darkSwitch") === "dark";
17 | darkSwitch.checked = darkThemeSelected;
18 | darkThemeSelected
19 | ? document.body.setAttribute("data-theme", "dark")
20 | : document.body.removeAttribute("data-theme");
21 | if (darkThemeSelected) {
22 | DarkIcon.innerHTML = '';
23 | toggleDarkElements();
24 | } else {
25 | DarkIcon.innerHTML = "☀";
26 | }
27 | }
28 |
29 | function resetTheme() {
30 | if (darkSwitch.checked) {
31 | document.body.setAttribute("data-theme", "dark");
32 | localStorage.setItem("darkSwitch", "dark");
33 | DarkIcon.innerHTML = '';
34 | toggleDarkElements();
35 | } else {
36 | document.body.removeAttribute("data-theme");
37 | localStorage.removeItem("darkSwitch");
38 | DarkIcon.innerHTML = "☀";
39 | toggleDarkElements();
40 | }
41 | }
42 |
43 | function toggleDarkElements() {
44 | var DropDown = document.getElementById("drop-down");
45 | if (DropDown !== null) {
46 | if (!DropDown.classList.contains("dropdown-menu-dark")) {
47 | DropDown.classList.add("dropdown-menu-dark");
48 | } else {
49 | DropDown.classList.remove("dropdown-menu-dark");
50 | }
51 | }
52 | var navbar = document.getElementById("main-nav");
53 | if (navbar !== null) {
54 | if (navbar.classList.contains("navbar-dark")) {
55 | navbar.classList.remove("navbar-dark");
56 | navbar.classList.add("navbar-light");
57 | } else {
58 | navbar.classList.remove("navbar-light");
59 | navbar.classList.add("navbar-dark");
60 | }
61 | }
62 | var list = document.getElementById("torrent-list");
63 | if (list === null) {
64 | list = document.getElementById("dir-list");
65 | }
66 | if (list === null) {
67 | list = document.getElementById("results");
68 | }
69 | if (list !== null) {
70 | for (var i = 0; i < list.children.length; i++) {
71 | if (list.children[i].classList.contains("text-white")) {
72 | list.children[i].classList.remove("text-white");
73 | list.children[i].style.backgroundColor = "white";
74 | } else {
75 | list.children[i].classList.add("text-white");
76 | list.children[i].style.backgroundColor = "#212529";
77 | }
78 | }
79 | }
80 | var s = document.getElementById("system-info");
81 | if (s !== null) {
82 | if (s.style.backgroundColor === "white") {
83 | s.style.backgroundColor = "#212529";
84 | } else {
85 | s.style.backgroundColor = "white";
86 | }
87 | }
88 | }
89 |
90 | function IsDark() {
91 | return document.body.getAttribute("data-theme") === "dark";
92 | }
--------------------------------------------------------------------------------
/static/search.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
40 |
41 |
42 |
50 |
51 |
Results for: "Top
52 | Trending"
53 | (0 results)
54 | Jump to: Tpb |
55 | Kickass |
56 | BitTorrent |
57 | Yts |
58 | Rarbg |
59 | Torrentz |
60 | 1337x |
61 | ImDB
62 |
63 |
64 | Torrents
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
75 |
76 |
80 |
81 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Cloud Torrent
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
43 |
44 |
45 |
46 |
85 |
86 |
87 |
88 |
89 |
90 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/static/js/torr.js:
--------------------------------------------------------------------------------
1 | var table = document.getElementById("files-table");
2 |
3 | function Log() {
4 | console.log("Magnet Link: ");
5 | }
6 |
7 | function addTorrent() {
8 | var Input = document.getElementById("input");
9 | if (Input.value == "") {
10 | ToastMessage("Please enter a magnet link.", "danger");
11 | return;
12 | }
13 | var magnet = Input.value;
14 | $.ajax({
15 | url: "/api/add",
16 | type: "POST",
17 | data: {
18 | magnet: magnet,
19 | },
20 | success: function (data) {
21 | ToastMessage("Torrent added successfully.", "success");
22 | Input.value = "";
23 | },
24 | error: function (data) {
25 | ToastMessage("Error adding torrent, " + data.responseText, "danger");
26 | },
27 | });
28 | }
29 |
30 | function getTorrents() {
31 | $.ajax({
32 | url: "/api/torrents",
33 | type: "GET",
34 | success: function (data) {
35 | updateTorrentList(data);
36 | },
37 | });
38 | }
39 |
40 | function updateTorrentList(data) {
41 | var torrents = JSON.parse(data);
42 | var list = $("#torrent-list");
43 | list.empty();
44 | if (torrents == null || torrents.length == 0) {
45 | var a =
46 | "";
47 | if (IsDark()) {
48 | a =
49 | "";
50 | }
51 | a += "";
52 | a += "
No torrents
";
53 | a += "";
54 | a +=
55 | "There are no torrents currently being downloaded.
";
56 | a += "";
57 | list.append(a);
58 | return;
59 | }
60 | for (var i = 0; i < torrents.length; i++) {
61 | var torrent = torrents[i];
62 | var a =
63 | "";
64 | if (IsDark()) {
65 | a =
66 | "";
67 | }
68 | a += "";
69 | speed = "";
70 | if (torrent.status == "Downloading") {
71 | speed = `
[` + torrent.speed + `]`;
72 | }
73 | a +=
74 | "
" +
75 | torrent.name +
76 | speed +
77 | "
";
78 | a += "
" + torrent.size + "";
79 | a += "
";
80 | a += "" + torrent.status;
81 | if (torrent.status == "Downloading" && torrent.eta != "") {
82 | a += ` [` + torrent.eta + `]`;
83 | }
84 | a += "
";
85 | a += "";
86 | a +=
87 | "
";
94 | a += "
";
95 | a += ``;
96 | a += `
`;
97 | a += `
`;
98 | a += `
`;
99 | if (
100 | torrent.status == "Downloading" ||
101 | torrent.status == "Fetching Metadata"
102 | ) {
103 | a += `
`;
104 | } else if (torrent.status == "Completed") {
105 | a += `
`;
106 | } else if (torrent.status == "Stopped") {
107 | a += `
`;
108 | }
109 | a += `
`;
110 | a += "
";
111 | list.append(a);
112 | }
113 | }
114 |
115 | function getBarColor(status) {
116 | if (status == "Downloading") {
117 | return "primary";
118 | } else if (status == "Completed") {
119 | return "success";
120 | } else if (status == "Paused") {
121 | return "warning";
122 | } else if (status == "Stopped") {
123 | return "danger";
124 | } else {
125 | return "secondary";
126 | }
127 | }
128 |
129 | function removeTorrent(id) {
130 | $.ajax({
131 | url: "/api/remove",
132 | type: "POST",
133 | data: {
134 | uid: id,
135 | },
136 | success: function (data) {
137 | ToastMessage("Torrent removed successfully.", "success");
138 | getTorrents();
139 | },
140 | });
141 | }
142 |
143 | function pauseTorrent(id) {
144 | $.ajax({
145 | url: "/api/pause",
146 | type: "POST",
147 | data: {
148 | uid: id,
149 | },
150 | success: function (data) {
151 | ToastMessage("Torrent paused successfully.", "primary");
152 | getTorrents();
153 | },
154 | });
155 | }
156 |
157 | function resumeTorrent(id) {
158 | $.ajax({
159 | url: "/api/resume",
160 | type: "POST",
161 | data: {
162 | uid: id,
163 | },
164 | success: function (data) {
165 | ToastMessage("Torrent resumed successfully.", "success");
166 | getTorrents();
167 | },
168 | });
169 | }
170 |
171 | function stopAll() {
172 | $.ajax({
173 | url: "/api/stopall",
174 | type: "POST",
175 | success: function (data) {
176 | ToastMessage("All torrents stopped.", "danger");
177 | getTorrents();
178 | },
179 | });
180 | }
181 |
182 | function startAll() {
183 | $.ajax({
184 | url: "/api/startall",
185 | type: "POST",
186 | success: function (data) {
187 | ToastMessage("All torrents started.", "primary");
188 | getTorrents();
189 | },
190 | });
191 | }
192 |
193 | const torr = new EventSource("/torrents/update");
194 |
195 | torr.addEventListener(
196 | "torrents",
197 | (e) => {
198 | updateTorrentList(e.data);
199 | },
200 | false
201 | );
202 |
203 | function GetSystemInfo() {
204 | $.ajax({
205 | url: "/api/status",
206 | type: "GET",
207 | success: function (data) {
208 | WriteSysInfo(data);
209 | },
210 | });
211 | }
212 | setInterval(GetSystemInfo, 10000);
213 |
214 | function WriteSysInfo(data) {
215 | sys = document.getElementById("system-info");
216 | sys.innerHTML = "";
217 | html_ =
218 | `;CPU: ` +
219 | data.cpu +
220 | `, Memory: ` +
221 | data.mem +
222 | `, Disk: ` +
223 | data.disk +
224 | `, OS: ` +
225 | data.os +
226 | `, Arch: ` +
227 | data.arch +
228 | `, Downloads: ` +
229 | data.downloads +
230 | `, Folder: /downloads/torrents/
`;
231 | sys.innerHTML = html_;
232 | }
233 |
234 | function removeAll() {
235 | $.ajax({
236 | url: "/api/removeall",
237 | type: "POST",
238 | success: function (data) {
239 | ToastMessage("All torrents removed successfully.", "success");
240 | getTorrents();
241 | },
242 | });
243 | }
244 |
245 | GetSystemInfo();
246 | getTorrents();
247 | ToastMessage("Welcome to the Torrent Manager.", "success");
248 |
--------------------------------------------------------------------------------
/static/js/main.js:
--------------------------------------------------------------------------------
1 | var Audd = new Audio();
2 | var table = document.getElementById("files-table");
3 | var CurrentPlaying = [null, null];
4 |
5 | function updateDirList(url) {
6 | if (url == null) {
7 | url = "/dir/" + window.location.pathname.replace("/downloads", "")
8 | }
9 | $.ajax({
10 | url: url,
11 | type: "GET",
12 | dataType: "json",
13 | success: function (data) {
14 | var dirList = document.getElementById("dir-list");
15 | dirList.innerHTML = "";
16 | if (data.length === 0) {
17 | data.push({ "name": "No Files...", "path": "", "type": "dir" });
18 | }
19 | for (var i = 0; i < data.length; i++) {
20 | var dir = data[i];
21 | var a = ``;
22 | if (IsDark()) {
23 | a = ``;
24 | }
25 | a += "";
26 | if (dir.is_dir == "true") {
27 | a += `
${dir.name}
`;
28 | } else {
29 | a += `
${dir.name}${dir.ext}
`;
30 | }
31 | a += "
" + dir.size + "";
32 | a += "
";
33 | a += "" + dir.type + "
";
34 | a += ``;
35 | a += ``;
36 | a += `
`;
37 | if (dir.is_dir == "true") {
38 | a += ``;
39 | a += ``;
40 | } else {
41 | a += ``;
42 | if (dir.type == "Video") {
43 | a += ``;
44 | if (dir.ext == ".mkv") {
45 | a += ``;
46 | }
47 | } else if (dir.type == "Audio") {
48 | a += ``;
49 | } else if (dir.type == "Image") {
50 | a += ``;
51 | }
52 | }
53 | a += ``;
54 | a += ``;
55 | a += "
";
56 | a += "";
57 | dirList.innerHTML += a;
58 | }
59 | },
60 | error: function (err) {
61 | console.log(err);
62 | },
63 | });
64 | }
65 |
66 |
67 | function downloadStart(e) {
68 | var path = e.getAttribute("data-path");
69 | var name = path.split("/").pop();
70 | var a = document.createElement("a");
71 | a.href = path;
72 | a.download = name;
73 | a.click();
74 | }
75 |
76 | function playAudio(url, uid) {
77 | Audd.src = url;
78 | Audd.play();
79 | var btn = document.getElementById(uid);
80 | btn.outerHTML =
81 | ``;
86 | ToastMessage("Playing " + url.split("/").pop(), "primary");
87 | if (CurrentPlaying[0] != null && CurrentPlaying[0] != uid) {
88 | var btn = document.getElementById(CurrentPlaying[0]);
89 | btn.outerHTML =
90 | ``;
95 | }
96 | CurrentPlaying = [uid, url];
97 | Audd.onended = function () {
98 | btn.outerHTML =
99 | ``;
104 | CurrentPlaying = [null, null];
105 | };
106 | }
107 |
108 | function pauseAudio(url, uid) {
109 | Audd.pause();
110 | var btn = document.getElementById(uid);
111 | btn.outerHTML =
112 | ``;
117 | ToastMessage("Paused Audio", "primary");
118 | }
119 |
120 | function showImage(e) {
121 | url = $(e).data("src");
122 | var modal = document.getElementById("main-modal");
123 | var img = document.getElementById("main-modal-img");
124 | var captionText = document.getElementById("main-modal-caption");
125 | var closeSpan = document.getElementsByClassName("close")[0];
126 | modal.style.display = "block";
127 | img.src = url;
128 | captionText.innerHTML = url;
129 | closeSpan.onclick = function () {
130 | modal.style.display = "none";
131 | };
132 | }
133 |
134 |
135 | function playVideo(e) {
136 | var url = $(e).data("src");
137 | window.location.href = "/stream/" + url;
138 | }
139 |
140 | function deleteFile(e) {
141 | var current_url = window.location.href;
142 | var path = $(e).data("path");
143 | var name = path.split("/").pop();
144 | var r = confirm("Are you sure you want to delete " + name + "?");
145 | if (r !== true) {
146 | return;
147 | }
148 | path = path.replace("/dir/", "");
149 | console.log(path);
150 | $.ajax({
151 | url: "/api/deletefile/" + path,
152 | type: "GET",
153 | success: function (data) {
154 | ToastMessage("Deleted " + name, "danger");
155 | updateDirList(current_url.replace("downloads", "dir"));
156 | },
157 | error: function (err) {
158 | ToastMessage("Failed to delete " + err.responseText, "danger");
159 | },
160 | });
161 | }
162 |
163 | function backButton() {
164 | var path = window.location.pathname;
165 | if (path.length > 1) {
166 | var newPath = path.substring(0, path.lastIndexOf("/"));
167 | window.location.href = newPath;
168 | }
169 | }
170 |
171 | updateDirList();
172 | if (window.location.pathname == "/downloads/") {
173 | ToastMessage("Welcome to File Manager", "primary");
174 | }
175 |
176 | const handleFileUpload = (e) => {
177 | const files = e.target.files;
178 | const uploadData = new FormData();
179 | uploadData.append("file", files[0]);
180 | uploadData.append("path", window.location.pathname);
181 | fetch("/api/upload", {
182 | method: "POST",
183 | body: uploadData,
184 | }).then((response) => {
185 | ToastMessage("Uploaded " + files[0].name, "success");
186 | UpdateDir();
187 | });
188 | };
189 |
190 | document.querySelector("#file").addEventListener("change", (event) => {
191 | handleFileUpload(event);
192 | });
193 |
194 | function CreateFolder() {
195 | var name = prompt("Enter Folder Name");
196 | if (name != null) {
197 | $.ajax({
198 | url: "/api/create/" + window.location.pathname + name,
199 | type: "GET",
200 | success: function (data) {
201 | ToastMessage("Created " + name, "primary");
202 | updateDirList();
203 | },
204 | error: function (data) {
205 | ToastMessage("Error: " + data.responseText, "danger");
206 | },
207 | });
208 | } else {
209 | ToastMessage("Name cannot be null", "danger");
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/torrent.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io/ioutil"
7 | "log"
8 | "net/http"
9 | "net/url"
10 | "os"
11 | "path/filepath"
12 | "strconv"
13 | "strings"
14 | "time"
15 |
16 | "github.com/cenkalti/rain/torrent"
17 | )
18 |
19 | var (
20 | client *torrent.Session
21 | hClient = &http.Client{Timeout: time.Second * 10}
22 | Trackers []string
23 | )
24 |
25 | func InitClient() *torrent.Session {
26 | config := torrent.DefaultConfig
27 | config.DataDir = Root + "/torrents/"
28 | config.Database = Root + "/torrents.db"
29 | client, err := torrent.NewSession(config)
30 | if err != nil {
31 | log.Fatal(err)
32 | }
33 | return client
34 | }
35 |
36 | type TorrentData struct {
37 | Name string `json:"name,omitempty"`
38 | Size string `json:"size,omitempty"`
39 | Status string `json:"status,omitempty"`
40 | Magnet string `json:"magnet,omitempty"`
41 | ID string `json:"id,omitempty"`
42 | UID string `json:"uid,omitempty"`
43 | Perc string `json:"perc,omitempty"`
44 | Eta string `json:"eta,omitempty"`
45 | Speed string `json:"speed,omitempty"`
46 | Progress string `json:"progress,omitempty"`
47 | Icon string `json:"icon,omitempty"`
48 | Path string `json:"path,omitempty"`
49 | }
50 |
51 | func AddTorrentByMagnet(magnet string) (bool, error) {
52 | if CheckDuplicateTorrent(magnet) {
53 | return false, fmt.Errorf("torrent already exists")
54 | }
55 | m, err := client.AddURI(magnet, &torrent.AddTorrentOptions{StopAfterDownload: true})
56 | if err != nil {
57 | return false, err
58 | }
59 | for i := range Trackers {
60 | m.AddTracker(Trackers[i])
61 | }
62 | return true, nil
63 | }
64 |
65 | func GetTrakers() {
66 | url := "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all.txt"
67 | resp, err := hClient.Get(url)
68 | if err != nil {
69 | return
70 | }
71 | defer resp.Body.Close()
72 | t, _ := ioutil.ReadAll(resp.Body)
73 | for _, line := range strings.Split(string(t), "\n") {
74 | if strings.HasPrefix(line, "udp://") {
75 | Trackers = append(Trackers, line)
76 | }
77 | }
78 | }
79 |
80 | func DeleteTorrentByID(id string) (bool, error) {
81 | if Torr := client.GetTorrent(id); Torr != nil {
82 | err := client.RemoveTorrent(id)
83 | return true, err
84 | }
85 | return false, nil
86 | }
87 |
88 | func PauseTorrentByID(id string) (bool, error) {
89 | if t := client.GetTorrent(id); t != nil {
90 | err := t.Stop()
91 | if err != nil {
92 | return false, err
93 | }
94 | return true, nil
95 | }
96 | return false, nil
97 | }
98 |
99 | func ResumeTorrentByID(id string) (bool, error) {
100 | if t := client.GetTorrent(id); t != nil {
101 | err := t.Start()
102 | if err != nil {
103 | return false, err
104 | }
105 | return true, nil
106 | }
107 | return false, nil
108 | }
109 |
110 | func GetTorrentByID(id string) TorrentData {
111 | t := client.GetTorrent(id)
112 | Stats, Icon := GetStats(t)
113 | Perc := GetDownloadPercentage(t)
114 | var Path = ServerPath(GetTorrentPath(t))
115 | if f, err := os.Stat(Path); err != nil && os.IsNotExist(err) || !f.IsDir() {
116 | Path = strings.Replace(Path, filepath.Base(Path), "", 1)
117 | }
118 | if t != nil {
119 | torrent := TorrentData{
120 | Name: t.Stats().Name,
121 | Size: ByteCountSI(t.Stats().Bytes.Total),
122 | Status: Stats,
123 | Magnet: t.Stats().InfoHash.String(),
124 | UID: t.ID(),
125 | Perc: Perc,
126 | Eta: fmt.Sprint(t.Stats().ETA),
127 | Speed: GetDownloadSpeed(t),
128 | Progress: GetProgress(Perc),
129 | Icon: Icon,
130 | Path: Path,
131 | }
132 | return torrent
133 | }
134 | return TorrentData{}
135 | }
136 |
137 | func StopAll() {
138 | client.StopAll()
139 | }
140 |
141 | func StartAll() {
142 | client.StartAll()
143 | }
144 |
145 | func DropAllTorrents() error {
146 | log.Println("Dropping all torrents")
147 | var err error
148 | for _, t := range GetTorrents() {
149 | err = client.RemoveTorrent(t.ID())
150 | }
151 | return err
152 | }
153 |
154 | func GetTorrents() []*torrent.Torrent {
155 | return client.ListTorrents()
156 | }
157 |
158 | func GetTorrentPath(Torr *torrent.Torrent) string {
159 | return "/downloads/torrents/" + Torr.ID() + "/" + Torr.Stats().Name
160 | }
161 |
162 | func GetAllTorrents() []TorrentData {
163 | var Torrents []TorrentData
164 | for _, t := range GetTorrents() {
165 | Perc := GetDownloadPercentage(t)
166 | Name := t.Stats().Name
167 | Icon := "bi bi-pause-circle"
168 | if Name == "" {
169 | Name = "fetching metadata..."
170 | }
171 | var Path = ServerPath(GetTorrentPath(t))
172 | if f, err := os.Stat(Path); err != nil && os.IsNotExist(err) || !f.IsDir() {
173 | Path = strings.Replace(Path, filepath.Base(Path), "", 1)
174 | }
175 | Stats, Icon := GetStats(t)
176 | Torrents = append(Torrents, TorrentData{
177 | Name: Name,
178 | Size: ByteCountSI(t.Stats().Bytes.Total),
179 | Status: Stats,
180 | Magnet: t.Stats().InfoHash.String(),
181 | UID: t.ID(),
182 | Perc: Perc,
183 | Eta: fmt.Sprint(t.Stats().ETA),
184 | Speed: GetDownloadSpeed(t),
185 | Progress: GetProgress(Perc),
186 | Icon: Icon,
187 | Path: Path,
188 | })
189 | }
190 | Torrents = SortAlpha(Torrents)
191 | for i := range Torrents {
192 | Torrents[i].ID = strconv.Itoa(i + 1)
193 | }
194 | return Torrents
195 | }
196 |
197 | func GetDownloadPercentage(torr *torrent.Torrent) string {
198 | if torr != nil {
199 | if torr.Stats().Pieces.Total != 0 {
200 | return fmt.Sprintf("%.2f", float64(torr.Stats().Pieces.Have)/float64(torr.Stats().Pieces.Total)*100) + "%"
201 | }
202 | }
203 | return "0%"
204 | }
205 |
206 | func GetProgress(perc string) string {
207 | return strings.Replace(perc, "%", "", 1)
208 | }
209 |
210 | func GetTorrentSize(id string) int64 {
211 | torr := client.GetTorrent(id)
212 | if torr != nil {
213 | if torr.Stats().Bytes.Total != 0 {
214 | return torr.Stats().Bytes.Total
215 | }
216 | }
217 | return 0
218 | }
219 |
220 | func GetDownloadSpeed(t *torrent.Torrent) string {
221 | if t.Stats().Speed.Download != 0 {
222 | return ByteCountSI(int64(t.Stats().Speed.Download)) + "/s"
223 | } else {
224 | return "-/-"
225 | }
226 | }
227 |
228 | func CheckDuplicateTorrent(magnet string) bool {
229 | magnet = ParseHashFromMagnet(magnet)
230 | for _, t := range GetTorrents() {
231 | if strings.ToLower(t.Stats().InfoHash.String()) == magnet {
232 | return true
233 | }
234 | }
235 | return false
236 | }
237 |
238 | func ParseHashFromMagnet(magnet string) string {
239 | var args []string
240 | if strings.Contains(magnet, "&") {
241 | args = strings.Split(magnet, "&")
242 | } else {
243 | args = []string{magnet}
244 | }
245 | argv := strings.Split(args[0], "btih:")
246 | log.Println(argv)
247 | if len(argv) <= 1 {
248 | return ""
249 | }
250 | return strings.ToLower(argv[1])
251 | }
252 |
253 | func GetStats(torr *torrent.Torrent) (string, string) {
254 | if torr != nil {
255 | if torr.Stats().Bytes.Total == 0 || torr.Stats().Status == torrent.DownloadingMetadata {
256 | return "Fetching Metadata", "bi bi-meta"
257 | } else if torr.Stats().Bytes.Downloaded >= torr.Stats().Bytes.Total {
258 | return "Completed", "bi bi-cloud-upload"
259 | } else if torr.Stats().Status == torrent.Downloading {
260 | return "Downloading", "bi bi-pause-circle"
261 | } else {
262 | if fmt.Sprint(torr.Stats().Status) == "Stopped" {
263 | return "Stopped", "bi bi-skip-start"
264 | } else {
265 | return fmt.Sprint(torr.Stats().Status), "bi bi-play-circle"
266 | }
267 | }
268 | }
269 | return "Error", "bi bi-bug"
270 | }
271 |
272 | func GatherSearchResults(query string) []byte {
273 | var tpb []SearchReq
274 | if query == "top100" {
275 | var top100 []TopTorr
276 | if resp, err := hClient.Get("https://tpb23.ukpass.co/apibay/precompiled/data_top100_all.json"); err != nil {
277 | return []byte("[]")
278 | } else {
279 | defer resp.Body.Close()
280 | if err = json.NewDecoder(resp.Body).Decode(&top100); err != nil {
281 | return []byte("[]")
282 | }
283 | for _, v := range top100 {
284 | tpb = append(tpb, SearchReq{
285 | Name: v.Name,
286 | Size: fmt.Sprint(int64(v.Size)),
287 | Seeders: fmt.Sprint(int64(v.Seeders)),
288 | Leechers: fmt.Sprint(int64(v.Leechers)),
289 | InfoHash: fmt.Sprint(v.InfoHash),
290 | })
291 | }
292 | }
293 | } else {
294 | if resp, err := hClient.Get("https://tpb23.ukpass.co/apibay/q.php" + "?q=" + url.QueryEscape(query) + "&cat=0"); err != nil {
295 | return []byte("[]")
296 | } else {
297 | defer resp.Body.Close()
298 | if err = json.NewDecoder(resp.Body).Decode(&tpb); err != nil {
299 | return []byte("[]")
300 | }
301 | }
302 | }
303 | for i, t := range tpb {
304 | tpb[i].Magnet = "magnet:?xt=urn:btih:" + t.InfoHash + "&dn=" + url.QueryEscape(t.Name)
305 | tpb[i].Size = ByteCountSI(StringToInt64(t.Size))
306 | }
307 | data, _ := json.Marshal(tpb)
308 | return data
309 | }
310 |
311 | func GetLenTorrents() int {
312 | return len(GetTorrents())
313 | }
314 |
315 | func init() {
316 | PrepareWD()
317 | GetTrakers()
318 | client = InitClient()
319 | }
320 |
--------------------------------------------------------------------------------
/helpers.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "archive/zip"
5 | "bufio"
6 | "bytes"
7 | "fmt"
8 | "io"
9 | "io/ioutil"
10 | "log"
11 | "net/http"
12 | "os"
13 | "os/exec"
14 | "path/filepath"
15 | "regexp"
16 | "runtime"
17 | "sort"
18 | "strconv"
19 | "strings"
20 |
21 | "github.com/shirou/gopsutil/disk"
22 | )
23 |
24 | type DiskStatus struct {
25 | All, Used, Free string
26 | }
27 |
28 | type FileInfo struct {
29 | ID string `json:"id,omitempty"`
30 | Name string `json:"name,omitempty"`
31 | Size string `json:"size,omitempty"`
32 | Type string `json:"type,omitempty"`
33 | Path string `json:"path,omitempty"`
34 | IsDir string `json:"is_dir,omitempty"`
35 | Ext string `json:"ext,omitempty"`
36 | Icon string `json:"icon,omitempty"`
37 | }
38 |
39 | type SysInfo struct {
40 | IP string `json:"ip,omitempty"`
41 | OS string `json:"os,omitempty"`
42 | Arch string `json:"arch,omitempty"`
43 | CPU string `json:"cpu,omitempty"`
44 | Mem string `json:"mem,omitempty"`
45 | Disk string `json:"disk,omitempty"`
46 | Downloads string `json:"downloads,omitempty"`
47 | }
48 |
49 | type TopTorr struct {
50 | Name string `json:"name,omitempty"`
51 | Size float64 `json:"size,omitempty"`
52 | Seeders float64 `json:"seeders,omitempty"`
53 | Leechers float64 `json:"leechers,omitempty"`
54 | InfoHash string `json:"info_hash,omitempty"`
55 | }
56 |
57 | type SearchReq struct {
58 | Name string `json:"name"`
59 | InfoHash string `json:"info_hash"`
60 | Leechers string `json:"leechers"`
61 | Seeders string `json:"seeders"`
62 | Size string `json:"size"`
63 | Magnet string `json:"magnet"`
64 | }
65 |
66 | type Handle struct {
67 | Path string
68 | Func func(http.ResponseWriter, *http.Request)
69 | }
70 |
71 | func DiskUsage(path string) DiskStatus {
72 | data, _ := disk.Usage(path)
73 | fs := DiskStatus{
74 | All: ByteCountSI(int64(data.Total)),
75 | Used: ByteCountSI(int64(data.Used)),
76 | Free: ByteCountSI(int64(data.Free)),
77 | }
78 | return fs
79 | }
80 |
81 | func MemUsage() string {
82 | var m runtime.MemStats
83 | runtime.ReadMemStats(&m)
84 | return ByteCountSI(int64(m.Alloc))
85 | }
86 |
87 | func GetName(f string) string {
88 | return strings.TrimSuffix(f, filepath.Ext(f))
89 | }
90 |
91 | func GetFileType(f string) (string, string) {
92 | f = strings.ToLower(f)
93 | if strings.HasSuffix(f, ".mp4") || strings.HasSuffix(f, ".avi") || strings.HasSuffix(f, ".mkv") || strings.HasSuffix(f, ".webm") {
94 | return "Video", "fluency/48/000000/video.png"
95 | } else if strings.HasSuffix(f, ".mp3") || strings.HasSuffix(f, ".wav") || strings.HasSuffix(f, ".flac") {
96 | return "Audio", "nolan/64/musical.png"
97 | } else if strings.HasSuffix(f, ".jpg") || strings.HasSuffix(f, ".png") || strings.HasSuffix(f, ".gif") || strings.HasSuffix(f, ".webp") {
98 | return "Image", "color-glass/48/000000/image.png"
99 | } else if strings.HasSuffix(f, ".pdf") {
100 | return "Pdf", "color/48/000000/pdf.png"
101 | } else if strings.HasSuffix(f, ".txt") {
102 | return "Text", "external-prettycons-flat-prettycons/47/000000/external-text-text-formatting-prettycons-flat-prettycons-1.png"
103 | } else if strings.HasSuffix(f, ".zip") || strings.HasSuffix(f, ".rar") || strings.HasSuffix(f, ".7z") {
104 | return "Archive", "external-gradients-pongsakorn-tan/64/000000/external-archive-file-and-document-gradients-pongsakorn-tan-4.png"
105 | } else if strings.HasSuffix(f, ".iso") {
106 | return "ISO", "external-justicon-lineal-color-justicon/64/000000/external-iso-file-file-type-justicon-lineal-color-justicon.png"
107 | } else if strings.HasSuffix(f, ".exe") {
108 | return "Exe", "bi bi-filetype-exe"
109 | } else if strings.HasSuffix(f, ".doc") || strings.HasSuffix(f, ".docx") {
110 | return "Doc", "bi bi-file-word"
111 | } else if strings.HasSuffix(f, ".xls") || strings.HasSuffix(f, ".xlsx") {
112 | return "Xls", "bi bi-file-earmark-excel"
113 | } else if strings.HasSuffix(f, ".ppt") || strings.HasSuffix(f, ".pptx") {
114 | return "Ppt", "bi bi-filetype-pptx"
115 | } else if strings.HasSuffix(f, ".torrent") {
116 | return "Torrent", "fluency/48/000000/utorrent.png"
117 | } else if strings.HasSuffix(f, ".py") {
118 | return "Python", "color/48/000000/python--v1.png"
119 | } else if strings.HasSuffix(f, ".go") {
120 | return "Go", "color/48/000000/golang.png"
121 | } else if strings.HasSuffix(f, ".js") {
122 | return "Js", "color/48/000000/javascript--v1.png"
123 | } else if strings.HasSuffix(f, ".json") {
124 | return "JSON", "bi bi-filetype-json"
125 | } else if strings.HasSuffix(f, ".html") {
126 | return "HTML", "color/48/000000/html-5--v1.png"
127 | } else if strings.HasSuffix(f, ".css") {
128 | return "CSS", "external-flaticons-flat-flat-icons/64/000000/external-css-web-development-flaticons-flat-flat-icons.png"
129 | } else if strings.HasSuffix(f, ".db") {
130 | return "Database", "color/48/000000/data-configuration.png"
131 | } else {
132 | return "Unknown", "bi bi-file-earmark"
133 | }
134 | }
135 |
136 | func DeleteFile(path string) error {
137 | if f, err := os.Stat(path); os.IsNotExist(err) {
138 | return err
139 | } else if f.IsDir() {
140 | return os.RemoveAll(path)
141 | } else {
142 | return os.Remove(path)
143 | }
144 | }
145 |
146 | func ByteCountSI(b int64) string {
147 | const unit = 1000
148 | if b < unit {
149 | return fmt.Sprintf("%d B", b)
150 | }
151 | div, exp := int64(unit), 0
152 | for n := b / unit; n >= unit; n /= unit {
153 | div *= unit
154 | exp++
155 | }
156 | return fmt.Sprintf("%.1f %cB",
157 | float64(b)/float64(div), "kMGTPE"[exp])
158 | }
159 |
160 | func GetIP(r *http.Request) string {
161 | forwarded := r.Header.Get("X-FORWARDED-FOR")
162 | if forwarded != "" {
163 | return forwarded
164 | }
165 | return r.RemoteAddr
166 | }
167 |
168 | func SortAlpha(sortData []TorrentData) []TorrentData {
169 | var data = sortData
170 | sort.Slice(data, func(p, q int) bool {
171 | return data[p].Name < data[q].Name
172 | })
173 | return data
174 | }
175 |
176 | func StringInSlice(a string, list []string) bool {
177 | for _, b := range list {
178 | if b == a {
179 | return true
180 | }
181 | }
182 | return false
183 | }
184 |
185 | func StringToInt64(s string) int64 {
186 | i, _ := strconv.ParseInt(s, 10, 64)
187 | return i
188 | }
189 |
190 | func GetOutboundPort() string {
191 | if p := os.Getenv("PORT"); p != "" {
192 | if !strings.HasPrefix(p, ":") {
193 | return ":" + p
194 | } else {
195 | return p
196 | }
197 | }
198 | return ":80"
199 | }
200 |
201 | func isDirectory(path string) (bool, error) {
202 | fileInfo, err := os.Stat(path)
203 | if err != nil {
204 | return false, err
205 | }
206 | return fileInfo.IsDir(), err
207 | }
208 |
209 | func GetDirContentsMap(path string) ([]FileInfo, error) {
210 | var files []FileInfo
211 | var DirWalk, err = ioutil.ReadDir(path)
212 | if err != nil {
213 | return files, err
214 | }
215 | for i, file := range DirWalk {
216 | var Size, Type, Ext, Icon string
217 | if file.IsDir() {
218 | Type = "Folder"
219 | DirSize, _ := DirSize(path + "/" + file.Name())
220 | Size = ByteCountSI(DirSize)
221 | } else {
222 | Size = ByteCountSI(file.Size())
223 | Type, Icon = GetFileType(file.Name())
224 | Ext = filepath.Ext(file.Name())
225 | }
226 | files = append(files, FileInfo{
227 | ID: strconv.Itoa(i),
228 | Name: GetName(file.Name()),
229 | Size: Size,
230 | Type: Type,
231 | Path: GetPath(path, file),
232 | IsDir: strconv.FormatBool(file.IsDir()),
233 | Ext: Ext,
234 | Icon: Icon,
235 | })
236 | }
237 | return files, nil
238 |
239 | }
240 |
241 | func AbsPath(path string) string {
242 | return filepath.ToSlash(path)
243 | }
244 |
245 | func ServerPath(path string) string {
246 | return strings.Replace(AbsPath(path), AbsPath(Root), "", 1)
247 | }
248 |
249 | func DirSize(path string) (int64, error) {
250 | var size int64
251 | err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
252 | if err != nil {
253 | return err
254 | }
255 | if !info.IsDir() {
256 | size += info.Size()
257 | }
258 | return err
259 | })
260 | return size, err
261 | }
262 |
263 | func PrepareWD() {
264 | if _, err := os.Stat(filepath.Join(Root, "torrents")); os.IsNotExist(err) {
265 | if err := os.MkdirAll(filepath.Join(Root, "torrents"), 0755); err != nil {
266 | panic(err)
267 | }
268 | }
269 | }
270 |
271 | func GetPath(path string, file os.FileInfo) string {
272 | if file.IsDir() {
273 | return "/downloads" + ServerPath(path+"/"+file.Name())
274 | } else {
275 | return "/dir" + ServerPath(path+"/"+file.Name())
276 | }
277 | }
278 |
279 | func ZipDir(path string, torrName string) (string, error) {
280 | var zipPath = filepath.Join(Root, "torrents", torrName+".zip")
281 | if _, err := os.Stat(zipPath); !os.IsNotExist(err) {
282 | return zipPath, err
283 | }
284 | if err := ZipFiles(zipPath, path); err != nil {
285 | return "", err
286 | }
287 | return zipPath, nil
288 | }
289 |
290 | func ZipFiles(filename string, folder string) error {
291 | newZipFile, err := os.Create(filename)
292 | if err != nil {
293 | return err
294 | }
295 | defer newZipFile.Close()
296 |
297 | zipWriter := zip.NewWriter(newZipFile)
298 | defer zipWriter.Close()
299 |
300 | currDir, _ := os.Getwd()
301 |
302 | if info, _ := os.Stat(folder); info.IsDir() {
303 | localFiles := []string{}
304 | filepath.Walk(folder, func(path string, info os.FileInfo, err error) error {
305 | if fileinfo, err := os.Stat(path); fileinfo.Mode().IsRegular() && err == nil {
306 | localFiles = append(localFiles, path)
307 | }
308 | return nil
309 | })
310 |
311 | count := int64(len(localFiles))
312 |
313 | log.Println("Number of files", count)
314 |
315 | upOne, err := filepath.Abs(filepath.Join(folder, ".."))
316 | os.Chdir(upOne)
317 | if err != nil {
318 | log.Println(err)
319 | }
320 | for _, loc := range localFiles {
321 | relpath, err := filepath.Rel(upOne, loc)
322 | if err != nil {
323 | log.Println(err)
324 | }
325 | if err = addFileToZip(zipWriter, filepath.Join(relpath)); err != nil {
326 | return err
327 | }
328 | }
329 | os.Chdir(currDir)
330 | return nil
331 | }
332 | return fmt.Errorf("not a directory")
333 | }
334 |
335 | func addFileToZip(zipWriter *zip.Writer, filename string) error {
336 |
337 | fileToZip, err := os.Open(filename)
338 | if err != nil {
339 | return err
340 | }
341 | defer fileToZip.Close()
342 | info, err := fileToZip.Stat()
343 | if err != nil {
344 | return err
345 | }
346 |
347 | header, err := zip.FileInfoHeader(info)
348 | if err != nil {
349 | return err
350 | }
351 |
352 | header.Name = filename
353 | header.Method = zip.Store
354 |
355 | writer, err := zipWriter.CreateHeader(header)
356 | if err != nil {
357 | return err
358 | }
359 | _, err = io.Copy(writer, fileToZip)
360 | return err
361 | }
362 |
363 | func GetRealtimeOutput(vid string) {
364 | command := fmt.Sprintf("ffmpeg -i '%s' -c:v libx265 -an -x265-params crf=25 OUT.mp4 -progress -", vid)
365 | cmd := exec.Command("/bin/bash", "-c", command)
366 | Frames, _ := GetTotalFramesInVideo(vid)
367 | stderr, _ := cmd.StderrPipe()
368 | cmd.Start()
369 | scanner := bufio.NewScanner(stderr)
370 | scanner.Split(bufio.ScanWords)
371 | for scanner.Scan() {
372 | m := scanner.Text()
373 | frameRe := regexp.MustCompile(`frame=\n(\d+)`) // fix regex
374 | if frameRe.MatchString(m) {
375 | fmt.Println(m)
376 | fmt.Println(Frames) // Working on FFMPEG Conversion
377 | }
378 | }
379 | cmd.Wait()
380 | }
381 |
382 | func GetTotalFramesInVideo(path string) (int, error) {
383 | cd := "ffprobe -v error -select_streams v:0 -count_packets -show_entries stream=nb_read_packets -of csv=p=0 '" + path + "'"
384 | var cmd = exec.Command("/bin/bash", "-c", cd)
385 | var out bytes.Buffer
386 | cmd.Stdout = &out
387 | err := cmd.Run()
388 | if err != nil {
389 | fmt.Println(err)
390 | }
391 | return strconv.Atoi(strings.ReplaceAll(out.String(), "\n", ""))
392 | }
393 |
--------------------------------------------------------------------------------
/handlers.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io"
7 | "log"
8 | "net/http"
9 | "net/url"
10 | "os"
11 | "path/filepath"
12 | "runtime"
13 | "strings"
14 | "time"
15 |
16 | "github.com/julienschmidt/sse"
17 | )
18 |
19 | var (
20 | SSEFeed = sse.New()
21 | )
22 |
23 | func GetTorrent(w http.ResponseWriter, r *http.Request) {
24 | defer func() {
25 | if err, ok := recover().(error); ok {
26 | http.Error(w, err.Error(), http.StatusInternalServerError)
27 | }
28 | }()
29 | r.ParseForm()
30 | id := r.FormValue("uid")
31 | if id == "" {
32 | http.Error(w, "No uid provided", http.StatusBadRequest)
33 | return
34 | }
35 | torrent := GetTorrentByID(id)
36 | if torrent.Status == "" {
37 | http.Error(w, "Torrent not found", http.StatusNotFound)
38 | return
39 | }
40 | w.Header().Set("Content-Type", "application/json")
41 | json.NewEncoder(w).Encode(torrent)
42 | }
43 |
44 | func AddTorrent(w http.ResponseWriter, r *http.Request) {
45 | defer func() {
46 | if err, ok := recover().(error); ok {
47 | http.Error(w, err.Error(), http.StatusInternalServerError)
48 | }
49 | }()
50 | r.ParseForm()
51 | magnet := r.FormValue("magnet")
52 | if magnet == "" {
53 | http.Error(w, "No magnet provided", http.StatusBadRequest)
54 | return
55 | }
56 | if ok, err := AddTorrentByMagnet(magnet); err != nil {
57 | http.Error(w, err.Error(), http.StatusInternalServerError)
58 | return
59 | } else if !ok {
60 | http.Error(w, "Torrent already exists", http.StatusBadRequest)
61 | return
62 | } else {
63 | w.WriteHeader(http.StatusOK)
64 | }
65 | }
66 |
67 | func DeleteTorrent(w http.ResponseWriter, r *http.Request) {
68 | defer func() {
69 | if err, ok := recover().(error); ok {
70 | http.Error(w, err.Error(), http.StatusInternalServerError)
71 | }
72 | }()
73 | r.ParseForm()
74 | id := r.FormValue("uid")
75 | if id == "" {
76 | http.Error(w, "No uid provided", http.StatusBadRequest)
77 | return
78 | }
79 | if ok, err := DeleteTorrentByID(id); err != nil {
80 | http.Error(w, err.Error(), http.StatusInternalServerError)
81 | return
82 | } else if !ok {
83 | http.Error(w, "Torrent not found", http.StatusNotFound)
84 | return
85 | } else {
86 | w.WriteHeader(http.StatusOK)
87 | }
88 | }
89 |
90 | func PauseTorrent(w http.ResponseWriter, r *http.Request) {
91 | defer func() {
92 | if err, ok := recover().(error); ok {
93 | http.Error(w, err.Error(), http.StatusInternalServerError)
94 | }
95 | }()
96 | r.ParseForm()
97 | id := r.FormValue("uid")
98 | if id == "" {
99 | http.Error(w, "No uid provided", http.StatusBadRequest)
100 | return
101 | }
102 | if ok, err := PauseTorrentByID(id); err != nil {
103 | http.Error(w, err.Error(), http.StatusInternalServerError)
104 | return
105 | } else if !ok {
106 | http.Error(w, "Torrent not found", http.StatusNotFound)
107 | return
108 | } else {
109 | w.WriteHeader(http.StatusOK)
110 | }
111 | }
112 |
113 | func ResumeTorrent(w http.ResponseWriter, r *http.Request) {
114 | defer func() {
115 | if err, ok := recover().(error); ok {
116 | http.Error(w, err.Error(), http.StatusInternalServerError)
117 | }
118 | }()
119 | r.ParseForm()
120 | id := r.FormValue("uid")
121 | if id == "" {
122 | http.Error(w, "No uid provided", http.StatusBadRequest)
123 | return
124 | }
125 | if ok, err := ResumeTorrentByID(id); err != nil {
126 | http.Error(w, err.Error(), http.StatusInternalServerError)
127 | return
128 | } else if !ok {
129 | http.Error(w, "Torrent not found", http.StatusNotFound)
130 | return
131 | } else {
132 | w.WriteHeader(http.StatusOK)
133 | }
134 | }
135 |
136 | func DropAll(w http.ResponseWriter, r *http.Request) {
137 | defer func() {
138 | if err, ok := recover().(error); ok {
139 | http.Error(w, err.Error(), http.StatusInternalServerError)
140 | }
141 | }()
142 | if err := DropAllTorrents(); err != nil {
143 | http.Error(w, err.Error(), http.StatusInternalServerError)
144 | return
145 | }
146 | w.WriteHeader(http.StatusOK)
147 | }
148 |
149 | func StartAllHandler(w http.ResponseWriter, r *http.Request) {
150 | defer func() {
151 | if err, ok := recover().(error); ok {
152 | http.Error(w, err.Error(), http.StatusInternalServerError)
153 | }
154 | }()
155 | StartAll()
156 | w.WriteHeader(http.StatusOK)
157 | }
158 |
159 | func StopAllHandler(w http.ResponseWriter, r *http.Request) {
160 | defer func() {
161 | if err, ok := recover().(error); ok {
162 | http.Error(w, err.Error(), http.StatusInternalServerError)
163 | }
164 | }()
165 | StopAll()
166 | w.WriteHeader(http.StatusOK)
167 | }
168 |
169 | func SystemStats(w http.ResponseWriter, r *http.Request) {
170 | defer func() {
171 | if err, ok := recover().(error); ok {
172 | http.Error(w, err.Error(), http.StatusInternalServerError)
173 | }
174 | }()
175 | Disk := DiskUsage(Root)
176 | Details := SysInfo{
177 | IP: GetIP(r),
178 | OS: runtime.GOOS,
179 | Arch: runtime.GOARCH,
180 | CPU: fmt.Sprint(runtime.NumCPU()),
181 | Mem: MemUsage(),
182 | Disk: fmt.Sprintf("%s/%s", Disk.Used, Disk.All),
183 | Downloads: fmt.Sprint(GetLenTorrents()),
184 | }
185 | b, _ := json.Marshal(Details)
186 | w.Header().Set("Content-Type", "application/json")
187 | w.Write(b)
188 | }
189 |
190 | func DeleteFileHandler(w http.ResponseWriter, r *http.Request) {
191 | defer func() {
192 | if err, ok := recover().(error); ok {
193 | http.Error(w, err.Error(), http.StatusInternalServerError)
194 | }
195 | }()
196 | path := strings.Replace(AbsPath(filepath.Join(Root, r.URL.Path)), "api/deletefile/", "", 1)
197 | if strings.Contains(path, "/downloads/downloads") {
198 | path = strings.Replace(path, "/downloads", "", 1)
199 | }
200 | if strings.Contains(path, "torrents.db") || r.URL.Path == "/api/deletefile/downloads/torrents" {
201 | http.Error(w, "Protected path, cant delete!", http.StatusBadRequest)
202 | return
203 | }
204 | if err := DeleteFile(path); err != nil {
205 | http.Error(w, err.Error(), http.StatusInternalServerError)
206 | return
207 | }
208 | w.WriteHeader(http.StatusOK)
209 | }
210 |
211 | func UploadFileHandler(w http.ResponseWriter, r *http.Request) {
212 | defer func() {
213 | if err, ok := recover().(error); ok {
214 | http.Error(w, err.Error(), http.StatusInternalServerError)
215 | }
216 | }()
217 | r.ParseMultipartForm(32 << 20)
218 | file, handler, err := r.FormFile("file")
219 | if err != nil {
220 | http.Error(w, err.Error(), http.StatusInternalServerError)
221 | return
222 | }
223 | defer file.Close()
224 | log.Printf("Uploaded file: %+v\n", handler.Filename)
225 | log.Printf("File size: %+v\n", handler.Size)
226 | log.Printf("MIME header: %+v\n", handler.Header)
227 | DirPath := AbsPath(strings.Replace(AbsPath(filepath.Join(Root, r.FormValue("path"))), "/downloads", "", 1))
228 | f, err := os.OpenFile(filepath.Join(DirPath, handler.Filename), os.O_WRONLY|os.O_CREATE, 0666)
229 | if err != nil {
230 | http.Error(w, err.Error(), http.StatusInternalServerError)
231 | return
232 | }
233 | defer f.Close()
234 | io.Copy(f, file)
235 | w.WriteHeader(http.StatusOK)
236 | }
237 |
238 | func CreateFolderHandler(w http.ResponseWriter, r *http.Request) {
239 | defer func() {
240 | if err, ok := recover().(error); ok {
241 | http.Error(w, err.Error(), http.StatusInternalServerError)
242 | }
243 | }()
244 | r.ParseForm()
245 | DirPath := AbsPath(strings.Replace(AbsPath(filepath.Join(Root, r.URL.Path)), "/api/create/downloads", "", 1))
246 | if err := os.MkdirAll(DirPath, 0777); err != nil {
247 | http.Error(w, err.Error(), http.StatusInternalServerError)
248 | return
249 | }
250 | w.WriteHeader(http.StatusOK)
251 | }
252 |
253 | func streamTorrentUpdate() {
254 | fmt.Println("Streaming Torrents started...")
255 | for range time.Tick(time.Millisecond * 600) {
256 | TORRENTS := GetAllTorrents()
257 | d, _ := json.Marshal(TORRENTS)
258 | SSEFeed.SendString("", "torrents", string(d))
259 | }
260 | }
261 |
262 | func ActiveTorrents(w http.ResponseWriter, r *http.Request) {
263 | defer func() {
264 | if err, ok := recover().(error); ok {
265 | http.Error(w, err.Error(), http.StatusInternalServerError)
266 | }
267 | }()
268 | TORRENTS := GetAllTorrents()
269 | d, _ := json.Marshal(TORRENTS)
270 | w.Write(d)
271 | }
272 |
273 | func GetDirContents(w http.ResponseWriter, r *http.Request) {
274 | defer func() {
275 | if err, ok := recover().(error); ok {
276 | http.Error(w, err.Error(), http.StatusInternalServerError)
277 | }
278 | }()
279 | path := strings.Replace(AbsPath(filepath.Join(Root, r.URL.Path)), "/dir", "", 1)
280 | if IsDir, err := isDirectory(path); err == nil && IsDir {
281 | if _, err := os.Stat(path); os.IsNotExist(err) {
282 | http.Error(w, "Directory not found", http.StatusNotFound)
283 | return
284 | }
285 | files, err := GetDirContentsMap(path)
286 | if err != nil {
287 | http.Error(w, err.Error(), http.StatusInternalServerError)
288 | return
289 | }
290 | if len(files) == 0 {
291 | w.Write([]byte("[]"))
292 | return
293 | }
294 | d, _ := json.Marshal(files)
295 | w.Write(d)
296 | } else {
297 | http.ServeFile(w, r, path)
298 | }
299 | }
300 |
301 | func AutoComplete(w http.ResponseWriter, r *http.Request) {
302 | defer func() {
303 | if err, ok := recover().(error); ok {
304 | http.Error(w, err.Error(), http.StatusInternalServerError)
305 | }
306 | }()
307 | r.ParseForm()
308 | q := r.Form.Get("q")
309 | if q == "" {
310 | http.Error(w, "No query", http.StatusBadRequest)
311 | return
312 | }
313 | var client = http.DefaultClient
314 | resp, err := client.Get("https://streamm4u.ws/searchJS?term=" + url.QueryEscape(q)) // Will improve
315 | if err != nil {
316 | http.Error(w, err.Error(), http.StatusInternalServerError)
317 | return
318 | }
319 | defer resp.Body.Close()
320 | var data []string
321 | json.NewDecoder(resp.Body).Decode(&data)
322 | b, _ := json.Marshal(data)
323 | w.Write(b)
324 | }
325 |
326 | func SearchTorrents(w http.ResponseWriter, r *http.Request) {
327 | defer func() {
328 | if err, ok := recover().(error); ok {
329 | http.Error(w, err.Error(), http.StatusInternalServerError)
330 | }
331 | }()
332 | r.ParseForm()
333 | q := r.Form.Get("q")
334 | if q == "" {
335 | http.Error(w, "No query", http.StatusBadRequest)
336 | return
337 | }
338 | w.Write(GatherSearchResults(q))
339 | }
340 |
341 | func ZipFolderHandler(w http.ResponseWriter, r *http.Request) {
342 | defer func() {
343 | if err, ok := recover().(error); ok {
344 |
345 | http.Error(w, err.Error(), http.StatusInternalServerError)
346 | }
347 | }()
348 | r.ParseForm()
349 | path := strings.Replace(AbsPath(filepath.Join(Root, r.URL.Path)), "api/zip/", "", 1)
350 | if strings.Contains(path, "/downloads/downloads") {
351 | path = strings.Replace(path, "/downloads", "", 1)
352 | }
353 | folderName := filepath.Base(path)
354 | _, err := ZipDir(path, folderName)
355 | if err != nil {
356 | http.Error(w, err.Error(), http.StatusInternalServerError)
357 | return
358 | }
359 | filePath := "/dir/torrents/" + folderName + ".zip"
360 | var data = map[string]string{
361 | "file": filePath,
362 | "name": folderName + ".zip",
363 | }
364 | d, _ := json.Marshal(data)
365 | w.Write(d)
366 | }
367 |
368 | func init() {
369 | var API = []Handle{
370 | {"/api/add", AddTorrent},
371 | {"/api/torrents", ActiveTorrents},
372 | {"/api/torrent", GetTorrent},
373 | {"/api/status", SystemStats},
374 | {"/api/remove", DeleteTorrent},
375 | {"/api/pause", PauseTorrent},
376 | {"/api/resume", ResumeTorrent},
377 | {"/api/search/", SearchTorrents},
378 | {"/api/autocomplete", AutoComplete},
379 | {"/api/removeall", DropAll},
380 | {"/api/stopall", StopAllHandler},
381 | {"/api/startall", StartAllHandler},
382 | {"/api/upload", UploadFileHandler},
383 | {"/api/create/", CreateFolderHandler},
384 | {"/api/deletefile/", DeleteFileHandler},
385 | {"/api/zip/", ZipFolderHandler},
386 | }
387 | for _, api := range API {
388 | http.HandleFunc(api.Path, api.Func)
389 | }
390 | // update Server Events
391 | http.Handle("/torrents/update", SSEFeed)
392 | // Serve files
393 | http.HandleFunc("/dir/", GetDirContents)
394 | }
395 |
--------------------------------------------------------------------------------
/assets/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Cloud Torrent
7 |
8 |
9 |
10 |
11 |
13 |
14 |
15 |
32 |
45 |
46 |
47 |
54 |
102 |
103 |
104 |
105 |
108 |
116 |
117 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
136 |
137 |
138 |
157 |
158 |
159 |
160 |
161 |
162 |
226 |
227 |
228 |
229 |
230 | Torrents
231 |
232 |
233 |
234 |
235 |
263 |
264 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
--------------------------------------------------------------------------------
/assets/js/app.js:
--------------------------------------------------------------------------------
1 | const currentTime = new Date();
2 | const completeSVG = ``;
3 | const foxSVG = ``
4 | const resumeSVG = ``
5 | const pauseSVG = ``
6 | const sleepSVG = ``
7 |
8 |
9 | function getTorrents() {
10 | $.ajax({
11 | url: '/api/torrents',
12 | type: 'GET',
13 | dataType: 'json',
14 | success: function (data) {
15 | html = '';
16 | if (data == null) {
17 | return $('#torrents').html('No torrents found
');
18 | }
19 | data.forEach(t => {
20 | var toggleButton = ``
21 | if (IsPaused(t)) {
22 | toggleButton = ``
23 | }
24 | var svg = foxSVG;
25 | if (t.progress == '100.00') {
26 | svg = completeSVG
27 | }
28 | html += `
29 |
30 |
40 |
41 |
${TrimString(t.name, 40)}
42 |
45 | ${t.size}
46 |
47 |
48 |
57 |
58 |
59 |
Browse
61 | ${toggleButton}
62 |
65 |
66 |
67 |
68 |
`
69 | });
70 | $('#torrents').html(html);
71 | }
72 | });
73 | }
74 |
75 | getTorrents();
76 |
77 | function UpdateEvent(data) {
78 | var data = JSON.parse(data);
79 | var card_uids = [];
80 | document.querySelectorAll('#torrents .card').forEach(function (card) {
81 | card_uids.push(card.id.split('|')[1]);
82 | })
83 | var torrent_uids = [];
84 | if (data !== null) {
85 | data.forEach(t => {
86 | var card = document.getElementById('card|' + t.uid);
87 | torrent_uids.push(t.uid);
88 | if (card) {
89 | card.querySelector('#speed').innerHTML = t.speed;
90 | card.querySelector('#eta').innerHTML = t.eta;
91 | card.querySelector('#progress').innerHTML = ``;
92 | card.querySelector('#size').innerHTML = t.size;
93 | card.querySelector('#title').innerHTML = TrimString(t.name, 40);
94 | var svg = foxSVG && t.progress === '100.00' ? completeSVG : foxSVG;
95 | card.querySelector('#svg').innerHTML = svg;
96 | }
97 | });
98 | } else {
99 | $('#torrents').html('No torrents found
');
100 | }
101 | card_uids.forEach(uid => {
102 | if (!torrent_uids.includes(uid)) {
103 | console.log(torrent_uids, card_uids, uid);
104 | var pcard = document.getElementById('card|' + uid)
105 | if (pcard) {
106 | pcard.remove();
107 | }
108 | }
109 | })
110 | }
111 |
112 | function getBarColor(torrent) {
113 | if (torrent.progress == '100.00') {
114 | return 'bg-green-500';
115 | } else if (torrent.progress == '0') {
116 | return 'bg-red-500';
117 | } else if (torrent.status == 'Stopped' && torrent.progress !== '100.00') {
118 | return 'bg-yellow-300';
119 | } else {
120 | return 'bg-blue-500';
121 | }
122 | }
123 |
124 | function IsPaused(torrent) {
125 | return torrent.status == 'Stopped' && torrent.progress !== '100.00';
126 | }
127 |
128 | const updater = new EventSource("/torrents/update");
129 | updater.addEventListener("torrents", (e) => { UpdateEvent(e.data); }, false);
130 |
131 | function AddTorrent() {
132 | $.ajax({
133 | url: '/api/add',
134 | type: 'POST',
135 | dataType: 'json',
136 | data: {
137 | magnet: $('#input-magnet').val(),
138 | },
139 | error: function (xhr, status, error) {
140 | tata.error("Failed Adding torrent", xhr.responseText);
141 | return;
142 | }
143 | });
144 | tata.success('Success!', 'Torrent added');
145 | $('#input-magnet').val('');
146 | getTorrents();
147 | }
148 |
149 |
150 | function DeleteTorrent(uid) {
151 | $.ajax({
152 | url: '/api/remove',
153 | type: 'POST',
154 | dataType: 'json',
155 | data: {
156 | uid: uid,
157 | },
158 | success: function (data) {
159 | console.log(data);
160 | },
161 | error: function (data) {
162 | console.log(data);
163 | }
164 | });
165 | }
166 |
167 | function ConfirmDelete(uid) {
168 | swal({
169 | title: "Are you sure?",
170 | text: "You will not be able to recover this torrent!",
171 | icon: "warning",
172 | buttons: ["Cancel", "Delete"],
173 | }).then((willDelete) => {
174 | if (willDelete) {
175 | $.ajax({
176 | url: '/api/torrent',
177 | type: 'GET',
178 | dataType: 'json',
179 | data: {
180 | uid: uid,
181 | },
182 | success: function (data) {
183 | tata.error('Deleted!', 'Deleted ' + TrimString(data.name, 20));
184 | DeleteTorrent(uid);
185 | }
186 | });
187 | }
188 | });
189 | }
190 |
191 | function toggleTorrent(uid, unpause) {
192 | var url = '/api/pause';
193 | if (unpause) {
194 | url = '/api/resume';
195 | }
196 | $.ajax({
197 | url: url,
198 | type: 'GET',
199 | data: {
200 | uid: uid,
201 | },
202 | success: function (data) {
203 | $.ajax({
204 | url: '/api/torrent',
205 | type: 'GET',
206 | dataType: 'json',
207 | data: {
208 | uid: uid,
209 | },
210 | success: function (data) {
211 | card = document.getElementById('card|' + uid);
212 | if (unpause) {
213 | card.querySelector('#progress').innerHTML = ``;
214 | card.querySelector('#toggle').outerHTML = ``;
215 | card.querySelector('#svg').innerHTML = foxSVG;
216 | tata.success("Done!", 'Resumed ' + TrimString(data.name, 20));
217 | } else {
218 | card.querySelector('#progress').innerHTML = ``;
219 | card.querySelector('#toggle').outerHTML = ``
220 | card.querySelector('#svg').innerHTML = sleepSVG;
221 | card.querySelector('#speed').innerHTML = '-/-';
222 | card.querySelector('#eta').innerHTML = ``;
223 | tata.warn("Done!", 'Paused ' + TrimString(data.name, 20));
224 | }
225 | }
226 | });
227 | }
228 | });
229 | }
230 |
231 |
232 |
233 | document.getElementById('submit-magnet').addEventListener('click', AddTorrent);
234 |
235 | function TrimString(str, s) {
236 | return str.substring(0, s) + '...';
237 | }
238 |
239 | // https://itunes.apple.com/search?term=Avengers&entity=movie
--------------------------------------------------------------------------------
/test-go/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | CloudTorrent
9 |
10 |
11 |
14 |
15 |
16 |
17 |
22 |
23 |
24 |
25 |
26 |
29 |
32 |
33 |
38 |
39 |
40 |
41 |
42 |
43 |
48 |
50 |
57 |
58 |
59 |
60 |
62 |
64 |
65 |
66 |
69 |
70 |
71 |
74 |
77 |
78 |
79 |
80 |
83 |
84 |
87 |
88 |
91 |
92 |
97 |
102 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
417 |
418 |
419 |
420 |
421 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
4 | github.com/SermoDigital/jose v0.0.0-20180104203859-803625baeddc/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA=
5 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
6 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
7 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
8 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
9 | github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
10 | github.com/alicebob/miniredis v2.4.6+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
11 | github.com/anacrolix/dht v0.0.0-20180412060941-24cbf25b72a4/go.mod h1:hQfX2BrtuQsLQMYQwsypFAab/GvHg8qxwVi4OJdR1WI=
12 | github.com/anacrolix/dht v0.0.0-20181129074040-b09db78595aa/go.mod h1:Ayu4t+5TsHQ07/P8XzRJqVofv7lU4R1ZTT7KW5+SPFA=
13 | github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
14 | github.com/anacrolix/go-libutp v0.0.0-20180522111405-6baeb806518d/go.mod h1:beQSaSxwH2d9Eeu5ijrEnHei5Qhk+J6cDm1QkWFru4E=
15 | github.com/anacrolix/go-libutp v0.0.0-20180808010927-aebbeb60ea05/go.mod h1:POY/GPlrFKRxnOKH1sGAB+NBWMoP+sI+hHJxgcgWbWw=
16 | github.com/anacrolix/log v0.0.0-20180412014343-2323884b361d/go.mod h1:sf/7c2aTldL6sRQj/4UKyjgVZBu2+M2z9wf7MmwPiew=
17 | github.com/anacrolix/log v0.0.0-20180808012509-286fcf906b48/go.mod h1:sf/7c2aTldL6sRQj/4UKyjgVZBu2+M2z9wf7MmwPiew=
18 | github.com/anacrolix/missinggo v0.0.0-20180522035225-b4a5853e62ff/go.mod h1:b0p+7cn+rWMIphK1gDH2hrDuwGOcbB6V4VXeSsEfHVk=
19 | github.com/anacrolix/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s=
20 | github.com/anacrolix/missinggo v0.0.0-20181129073415-3237bf955fed/go.mod h1:IN+9GUe7OxKMIs/XeXEbT/rMUolmJzmlZiXHS7FwD/Y=
21 | github.com/anacrolix/mmsg v0.0.0-20180515031531-a4a3ba1fc8bb/go.mod h1:x2/ErsYUmT77kezS63+wzZp8E3byYB0gzirM/WMBLfw=
22 | github.com/anacrolix/sync v0.0.0-20171108081538-eee974e4f8c1/go.mod h1:+u91KiUuf0lyILI6x3n/XrW7iFROCZCG+TjgK8nW52w=
23 | github.com/anacrolix/sync v0.0.0-20180611022320-3c4cb11f5a01/go.mod h1:+u91KiUuf0lyILI6x3n/XrW7iFROCZCG+TjgK8nW52w=
24 | github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778/go.mod h1:s735Etp3joe/voe2sdaXLcqDdJSay1O0OPnM0ystjqk=
25 | github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
26 | github.com/anacrolix/tagflag v0.0.0-20180605133421-f477c8c2f14c/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
27 | github.com/anacrolix/tagflag v0.0.0-20180803105420-3a8ff5428f76/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
28 | github.com/anacrolix/torrent v0.0.0-20180622074351-fefeef4ee9eb/go.mod h1:3vcFVxgOASslNXHdivT8spyMRBanMCenHRpe0u5vpBs=
29 | github.com/anacrolix/torrent v1.0.0/go.mod h1:N6lCILah/qQCk/gVjHsOnqaug7a5DqQyryCbZD1L188=
30 | github.com/anacrolix/utp v0.0.0-20180219060659-9e0e1d1d0572/go.mod h1:MDwc+vsGEq7RMw6lr2GKOEqjWny5hO5OZXRVNaBJ2Dk=
31 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
32 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
33 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
34 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
35 | github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
36 | github.com/br0xen/termbox-util v0.0.0-20170904143325-de1d4c83380e/go.mod h1:x9wJlgOj74OFTOBwXOuO8pBguW37EgYNx51Dbjkfzo4=
37 | github.com/br0xen/termbox-util v0.0.0-20200220160819-dc6d6950ba00/go.mod h1:x9wJlgOj74OFTOBwXOuO8pBguW37EgYNx51Dbjkfzo4=
38 | github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
39 | github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
40 | github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
41 | github.com/cenkalti/boltbrowser v0.0.0-20190327195521-ebed13c76690/go.mod h1:TRC4W+aL8EZntkBiCvi7AGR0MH3JdBZUEx29Xm8cVoU=
42 | github.com/cenkalti/log v1.0.0 h1:0SITaDyovlmHFLaV+qenYmDxh8TNgxbJscMyn4W8XWk=
43 | github.com/cenkalti/log v1.0.0/go.mod h1:Kbz0XnbnTBtcJN8yeRuPW/9SNtP1tx5SwjU+357jKYM=
44 | github.com/cenkalti/rain v1.8.8 h1:ehQkRAfQYzPGwfLcLF/PQabH2WNTFeAONoKFmt1Ak+E=
45 | github.com/cenkalti/rain v1.8.8/go.mod h1:A7tBle/gHSJo+HAqW++3nlX+i9yEc6HPVF2jShtGh9I=
46 | github.com/chihaya/chihaya v1.0.1-0.20191017040149-0a420fe05344 h1:sPhIxxsbeANHva7ZC1SPMactDgb9WxFusp55GX0dd+E=
47 | github.com/chihaya/chihaya v1.0.1-0.20191017040149-0a420fe05344/go.mod h1:mayPJi46jm0kSeYTNlUFQAkfhPWSgoWwfL6yN7FHeK8=
48 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
49 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
50 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
51 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
52 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
53 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
54 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
55 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
56 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
57 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
58 | github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
59 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
60 | github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
61 | github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
62 | github.com/elgatito/upnp v0.0.0-20180711183757-2f244d205f9a/go.mod h1:afkYpY8JAIL4341N7Zj9xJ5yTovsg6BkWfBFlCzIoF4=
63 | github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
64 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
65 | github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
66 | github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
67 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
68 | github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
69 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
70 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
71 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
72 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
73 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
74 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
75 | github.com/go-redsync/redsync v1.1.1/go.mod h1:QClK/s99KRhfKdpxLTMsI5mSu43iLp0NfOneLPie+78=
76 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
77 | github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
78 | github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
79 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
80 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
81 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
82 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
83 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
84 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
85 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
86 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
87 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
88 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
89 | github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
90 | github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
91 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
92 | github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
93 | github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
94 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
95 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
96 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
97 | github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8=
98 | github.com/gosuri/uiprogress v0.0.0-20170224063937-d0567a9d84a1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0=
99 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
100 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
101 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
102 | github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
103 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
104 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
105 | github.com/hokaccha/go-prettyjson v0.0.0-20210113012101-fb4e108d2519/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI=
106 | github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
107 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
108 | github.com/ipfs/go-ipfs v0.4.18/go.mod h1:iXzbK+Wa6eePj3jQg/uY6Uoq5iOwY+GToD/bgaRadto=
109 | github.com/jackpal/bencode-go v0.0.0-20180813173944-227668e840fa/go.mod h1:5FSBQ74yhCl5oQ+QxRPYzWMONFnxbL68/23eezsBI5c=
110 | github.com/jackpal/bencode-go v1.0.0 h1:lzbSPPqqSfWQnqVNe/BBY1NXdDpncArxShL10+fmFus=
111 | github.com/jackpal/bencode-go v1.0.0/go.mod h1:5FSBQ74yhCl5oQ+QxRPYzWMONFnxbL68/23eezsBI5c=
112 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
113 | github.com/jroimartin/gocui v0.5.0/go.mod h1:l7Hz8DoYoL6NoYnlnaX6XCNR62G7J5FfSW5jEogzaxE=
114 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
115 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
116 | github.com/juju/ratelimit v1.0.1 h1:+7AIFJVQ0EQgq/K9+0Krm7m530Du7tIz0METWzN0RgY=
117 | github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
118 | github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
119 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
120 | github.com/julienschmidt/sse v0.0.0-20190921213156-72db694fe9e6 h1:AbjaMap/vJN5AnfsdtO3qJW5hGcJ7hf09fWPamwREOc=
121 | github.com/julienschmidt/sse v0.0.0-20190921213156-72db694fe9e6/go.mod h1:O3z+B05HD3YfLlfmvbRP/338uqKudexlEBkwFxtGxHE=
122 | github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
123 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
124 | github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
125 | github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
126 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
127 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
128 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
129 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
130 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
131 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
132 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
133 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
134 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
135 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
136 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
137 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
138 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
139 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
140 | github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
141 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
142 | github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
143 | github.com/mattn/go-sqlite3 v1.7.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
144 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
145 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
146 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
147 | github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103/go.mod h1:o9YPB5aGP8ob35Vy6+vyq3P3bWe7NQWzf+JLiXCiMaE=
148 | github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
149 | github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
150 | github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
151 | github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
152 | github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
153 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
154 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
155 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
156 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
157 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
158 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
159 | github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
160 | github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
161 | github.com/multiformats/go-multihash v0.1.0 h1:CgAgwqk3//SVEw3T+6DqI4mWMyRuDwZtOWcJT0q9+EA=
162 | github.com/multiformats/go-multihash v0.1.0/go.mod h1:RJlXsxt6vHGaia+S8We0ErjhojtKzPP2AH4+kYM7k84=
163 | github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY=
164 | github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
165 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
166 | github.com/nictuku/dht v0.0.0-20201226073453-fd1c1dd3d66a h1:jCE9Fk4S0dnR133jsymN8ED3/c6g+8m415Z6WmmqRTc=
167 | github.com/nictuku/dht v0.0.0-20201226073453-fd1c1dd3d66a/go.mod h1:OQ6jH4HHRpskJXlLCOeB1ckBSXXD4PmnPaIaFSi1dtI=
168 | github.com/nictuku/nettools v0.0.0-20150117095333-8867a2107ad3 h1:q6P6rwaWsdWQlaDt0DYPtmpj37fq2S4/IrZQO0zx488=
169 | github.com/nictuku/nettools v0.0.0-20150117095333-8867a2107ad3/go.mod h1:m19Kd92g5zm0IuGkdZo/OHBSPp9mqGlevOJu00nBoYs=
170 | github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
171 | github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=
172 | github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
173 | github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
174 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
175 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
176 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
177 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
178 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
179 | github.com/powerman/rpc-codec v1.2.2 h1:BK0JScZivljhwW/vLLhZLtUgqSxc/CD3sHEs8LiwwKw=
180 | github.com/powerman/rpc-codec v1.2.2/go.mod h1:3Qr/y/+u3CwcSww9tfJMRn/95lB2qUdUeIQe7BYlLDo=
181 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
182 | github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
183 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
184 | github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
185 | github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
186 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
187 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
188 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
189 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
190 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
191 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
192 | github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
193 | github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
194 | github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
195 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
196 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
197 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
198 | github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
199 | github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
200 | github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
201 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
202 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
203 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
204 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
205 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
206 | github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
207 | github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
208 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
209 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
210 | github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
211 | github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
212 | github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
213 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
214 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
215 | github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
216 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
217 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
218 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
219 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
220 | github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
221 | github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
222 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
223 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
224 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
225 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
226 | github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
227 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
228 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
229 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
230 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
231 | github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8=
232 | github.com/syncthing/syncthing v0.14.48-rc.4/go.mod h1:nw3siZwHPA6M8iSfjDCWQ402eqvEIasMQOE8nFOxy7M=
233 | github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
234 | github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
235 | github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
236 | github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
237 | github.com/willf/bloom v0.0.0-20170505221640-54e3b963ee16/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8=
238 | github.com/youtube/vitess v2.1.1+incompatible/go.mod h1:hpMim5/30F1r+0P8GGtB29d0gWHr0IZ5unS+CG0zMx8=
239 | github.com/youtube/vitess v3.0.0-rc.3+incompatible h1:+mxAImN50PmcSt39GwG08nmjVvFL+arNbv1pxUxaG0s=
240 | github.com/youtube/vitess v3.0.0-rc.3+incompatible/go.mod h1:hpMim5/30F1r+0P8GGtB29d0gWHr0IZ5unS+CG0zMx8=
241 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
242 | github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
243 | github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
244 | github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
245 | github.com/zeebo/bencode v1.0.0 h1:zgop0Wu1nu4IexAZeCZ5qbsjU4O1vMrfCrVgUjbHVuA=
246 | github.com/zeebo/bencode v1.0.0/go.mod h1:Ct7CkrWIQuLWAy9M3atFHYq4kG9Ao/SsY5cdtCXmp9Y=
247 | go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
248 | go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
249 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
250 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
251 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
252 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
253 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
254 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
255 | golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0=
256 | golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
257 | golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
258 | golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
259 | golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
260 | golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
261 | golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
262 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
263 | golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
264 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
265 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
266 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
267 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
268 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
269 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
270 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
271 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
272 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
273 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
274 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
275 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
276 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
277 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
278 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
279 | golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
280 | golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
281 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
282 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
283 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
284 | golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
285 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
286 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
287 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
288 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
289 | golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
290 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
291 | golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
292 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
293 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
294 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
295 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
296 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
297 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
298 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
299 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
300 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
301 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
302 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
303 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
304 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
305 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
306 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
307 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
308 | golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
309 | golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
310 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
311 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
312 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
313 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
314 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
315 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
316 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
317 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
318 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
319 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
320 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
321 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
322 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
323 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
324 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
325 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
326 | lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
327 | lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
328 | lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
329 |
--------------------------------------------------------------------------------