├── 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 | logo 2 | ~ New In Development Version ~ 3 | 4 | screenshot 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 | [![Releases](https://img.shields.io/github/release/jpillora/cloud-torrent.svg)](https://github.com/jpillora/cloud-torrent/releases) [![Releases](https://img.shields.io/github/downloads/jpillora/cloud-torrent/total.svg)](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 |
16 |
17 | 40 |
41 |
42 |
43 |
44 |
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 | ``; 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 |
15 |
16 | 38 |
39 |
40 |
41 |
    42 |
    43 |
    44 |
    45 | 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 |
    15 |
    16 | 17 | 39 |
    40 | 41 |
    42 |
    43 | 45 |
    46 | 48 |
    49 |
    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 |
    17 |
    18 | 41 |
    42 |
    43 | 44 |
    45 |
    46 |
    85 | 86 |
    87 |
    88 |
      89 |
      90 |
      91 |
      92 |
      93 |
      94 |
      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 | 128 |
      129 |
      130 |
      131 |
      132 |
      133 | 136 |
      137 |
      138 | 157 |
      158 |
      159 |
      160 |
      161 |
      162 |
      163 |
      164 | 224 |
      225 |
      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 |
      49 |
      50 |

      51 |

      52 |
      53 |
      54 |

      55 |
      56 |
      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 |
      18 |
      19 |

      ~

      20 |
      21 |
      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 | 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 | --------------------------------------------------------------------------------