Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package main
- //import "C"
- import (
- "bufio"
- "crypto/rand"
- "encoding/json"
- "log"
- //"crypto/tls"
- "encoding/hex"
- "fmt"
- "net/http"
- "os"
- "path"
- "path/filepath"
- "strings"
- "time"
- //"time"
- "github.com/anacrolix/torrent"
- filedriver "github.com/goftp/file-driver"
- "github.com/goftp/server"
- )
- var (
- username string
- downloadfolder string
- ftpport int
- password string
- webport string
- root string
- usetls bool
- tlscert string
- tlskey string
- client *torrent.Client
- sessions = map[string]time.Time{}
- )
- func main() {
- data, err1 := os.ReadFile("config.json")
- if err1 != nil {
- log.Fatal(err1)
- }
- cleaned := cleanComments(string(data))
- var errs error
- username, password, downloadfolder, webport, ftpport, errs = parseConfig(cleaned)
- if errs != nil {
- panic(errs)
- }
- if usetls == true && tlscert == "" || tlskey == "" {
- log.Fatal("You must set tls cert! and tls key!")
- }
- root, _ = filepath.Abs(downloadfolder)
- cfg := torrent.NewDefaultClientConfig()
- /*
- if errd != nil {
- log.Fatal(errd)
- }
- */
- cfg.DataDir = downloadfolder
- /*
- conf := torrent.ClientConfig{
- DataDir: downloadfolder,
- Debug: false,
- }
- //cfg.HTTPUserAgent = "Mozilla 5.0"
- */
- var err error
- client, err = torrent.NewClient(cfg)
- if err != nil {
- panic(err)
- }
- /*
- if err != nil {
- panic(err)
- }
- */
- /*
- reader := bufio.NewReader(os.Stdin)
- fmt.pri
- */
- defer client.Close()
- magnets := make(chan string)
- http.HandleFunc("/gettorrent", func(w http.ResponseWriter, r *http.Request) {
- doesCookieExist(w, r)
- m := r.FormValue("addtorrent")
- if m != "" {
- magnets <- m
- http.Redirect(w, r, "/panel", http.StatusFound)
- } else {
- http.Error(w, "No magnet provided", http.StatusBadRequest)
- }
- http.Redirect(w, r, "/", http.StatusSeeOther)
- })
- //torrents := []*torrent.Torrent{}
- go func() {
- for m := range magnets {
- t, err := client.AddMagnet(m)
- if err != nil {
- fmt.Println("Error adding magnet:", err)
- continue
- }
- go monitorTorrent(t)
- }
- }()
- go func() { // launch a tiny goroutine to periodically clean stale cookies
- for {
- //now := time.Now()
- for id, exp := range sessions {
- now := time.Now()
- if now.After(exp) {
- delete(sessions, id)
- }
- }
- time.Sleep(40 * time.Second)
- }
- }()
- /*
- sig := make(chan os.Signal, 1)
- signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
- <-sig
- fm
- */
- http.HandleFunc("/", homeHandler)
- http.HandleFunc("/login", loginHandler)
- http.HandleFunc("/panel", managementPanel)
- http.HandleFunc("/panel-stream", panelStream)
- http.HandleFunc("/cancel", cancelHandler)
- http.HandleFunc("/pause", pauseHandler)
- http.HandleFunc("/resume", resumeHandler)
- http.HandleFunc("/downloads/", directoryList)
- fmt.Println("http://localhost:8080")
- go ftpServer()
- if usetls == true {
- err = http.ListenAndServeTLS(webport, tlscert, tlskey, nil)
- } else {
- err = http.ListenAndServe(webport, nil)
- }
- if err != nil {
- panic(err)
- }
- }
- func monitorTorrent(t *torrent.Torrent) {
- <-t.GotInfo()
- t.DownloadAll()
- fmt.Println("Started downloading:", t.Name())
- ticker := time.NewTicker(5 * time.Second)
- defer ticker.Stop()
- for {
- select {
- case <-ticker.C:
- st := t.Stats()
- fmt.Printf("[%s] Peers: %d, Downloaded: %d bytes\n",
- t.Name(), st.ActivePeers, st.BytesRead)
- // Check if all files are complete
- done := true
- for _, f := range t.Files() {
- if f.BytesCompleted() < f.Length() {
- done = false
- break
- }
- }
- if done {
- fmt.Printf("Torrent %s is complete!\n", t.Name())
- t.Drop()
- return
- }
- case <-t.Closed():
- fmt.Printf("Stopped monitoring %s\n", t.Name())
- return
- }
- }
- }
- func doesCookieExist(w http.ResponseWriter, r *http.Request) {
- cookie, err := r.Cookie("session")
- if err != nil {
- http.Redirect(w, r, "/", http.StatusSeeOther)
- return
- }
- sessionID := cookie.Value
- expiry, ok := sessions[sessionID]
- if !ok || time.Now().After(expiry) {
- http.Redirect(w, r, "/", http.StatusSeeOther)
- return
- }
- }
- func homeHandler(w http.ResponseWriter, r *http.Request) {
- //fmt.Println(r.RemoteAddr, "connected") // meh useless..alerts only neccessary for logins
- fmt.Fprint(w, `
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Login</title>
- </head>
- <style>
- body {
- font-family: "Segoe UI", Arial, sans-serif;
- background-color: #121212;
- color: #e0e0e0;
- margin: 0;
- padding: 0;
- }
- nav {
- background: #1f1f1f;
- padding: 12px 20px;
- box-shadow: 0 2px 4px rgba(0,0,0,0.6);
- }
- nav a {
- color: #ffffff;
- margin-right: 20px;
- text-decoration: none;
- font-weight: 500;
- font-size: 16px;
- }
- .mm {
- background: #1f1f1f;
- padding: 12px 20px;
- box-shadow: 0 2px 4px rgba(0,0,0,0.6);
- }
- nav a:hover {
- color: #4dabf7;
- }
- h1, h2 {
- margin: 20px;
- color: #ffffff;
- }
- form {
- margin: 20px;
- }
- label {
- margin-right: 15px;
- }
- input[type="submit"] {
- background: #4dabf7;
- border: none;
- padding: 6px 12px;
- color: #fff;
- cursor: pointer;
- border-radius: 4px;
- }
- input[type="submit"]:hover {
- background: #339af0;
- }
- input[type="text"] {
- padding: 8px;
- margin: 20px;
- width: 300px;
- border: 1px solid #444;
- border-radius: 4px;
- background: #1e1e1e;
- color: #e0e0e0;
- }
- table {
- width: 95%;
- margin: 20px auto;
- border-collapse: collapse;
- background: #1e1e1e;
- border-radius: 6px;
- overflow: hidden;
- }
- th {
- background: #2c2c2c;
- color: #ffffff;
- text-align: left;
- padding: 10px;
- }
- td {
- padding: 10px;
- border-bottom: 1px solid #333;
- }
- tr:hover {
- background-color: #2a2a2a;
- }
- a {
- color: #4dabf7;
- }
- a:hover {
- text-decoration: underline;
- }
- .pre {
- background: #1e1e1e;
- padding: 15px;
- border: 1px solid #333;
- border-radius: 6px;
- overflow-x: auto;
- margin: 20px;
- font-family: Consolas, monospace;
- }
- .m {
- font: bold 14px/1.5 "Lucida Grande", Helvetica, Arial, sans-serif;
- text-anchor: middle;
- }
- </style>
- <body>
- <h1 class="mm">Login</h1>
- <form method="POST" class="mm" action="/login">
- <label class="m">User:</label><input name="username"><br>
- <label class="m">Pass:</label><input type="password" name="password"><br>
- <input type="submit" class="pre" value="Login">
- </form>
- </body>
- </html>
- `)
- }
- func loginHandler(w http.ResponseWriter, r *http.Request) {
- if r.Method != http.MethodPost {
- http.Redirect(w, r, "/", http.StatusSeeOther)
- return
- }
- user := r.FormValue("username")
- pass := r.FormValue("password")
- if user == username && pass == password {
- sessionID := generateSessionID()
- expiry := time.Now().Add(16 * time.Minute)
- // Store the cookie in the map
- sessions[sessionID] = expiry
- fmt.Println("login attempted", r.RemoteAddr, r.Host) // notifies if someone logged in. also works as intrusion detection in case
- http.SetCookie(w, &http.Cookie{
- Name: "session",
- Value: sessionID,
- Path: "/",
- Expires: expiry,
- SameSite: http.SameSiteLaxMode,
- })
- http.Redirect(w, r, "/panel", http.StatusSeeOther)
- } else {
- fmt.Fprint(w, "<h1>Incorrect username or password try again</h1>")
- http.Redirect(w, r, "/", http.StatusSeeOther)
- }
- }
- func generateSessionID() string {
- b := make([]byte, 16)
- rand.Read(b)
- return hex.EncodeToString(b)
- }
- func managementPanel(w http.ResponseWriter, r *http.Request) {
- doesCookieExist(w, r)
- fmt.Fprint(w, `<!DOCTYPE html>
- <html>
- <head>
- <title>Administrator</title>
- <style>
- body {
- font-family: "Segoe UI", Arial, sans-serif;
- background-color: #121212;
- color: #e0e0e0;
- margin: 0;
- padding: 0;
- }
- table { border-collapse: collapse; width: 90%; margin: 20px auto; }
- th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
- th { background: #0078d7; color: #fff; }
- tr:nth-child(even) { background: #f9f9f9; }
- form { margin: 20px auto; width: 80%; }
- input[type=text] { width: 70%; padding: 6px; }
- button { padding: 6px 12px; background:#0078d7; color:#fff; border:none; }
- h1, h2 {
- margin: 20px;
- color: #ffffff;
- }
- input[type="submit"] {
- background: #4dabf7;
- border: none;
- padding: 6px 12px;
- color: #fff;
- cursor: pointer;
- border-radius: 4px;
- }
- input[type="submit"]:hover {
- background: #339af0;
- }
- input[type="text"] {
- padding: 8px;
- margin: 20px;
- width: 300px;
- border: 1px solid #444;
- border-radius: 4px;
- background: #1e1e1e;
- color: #e0e0e0;
- }
- table {
- width: 95%;
- margin: 20px auto;
- border-collapse: collapse;
- background: #1e1e1e;
- border-radius: 6px;
- overflow: hidden;
- }
- </style>
- </head>
- <body>
- <h1 style="text-align:center;">Active Torrents</h1>
- <form method="POST" action="/gettorrent">
- <input type="text" name="addtorrent" placeholder="Paste magnet link here">
- <button type="submit">Add Torrent</button>
- </form>
- <table>
- <thead>
- <tr>
- <th>Name</th>
- <th>Peers</th>
- <th>Downloaded</th>
- <th>Remaining</th>
- <th>Progress</th>
- <th>Actions</th>
- </tr>
- </thead>
- <tbody id="torrentTable">
- </tbody>
- </table>
- <script>
- const evtSource = new EventSource("/panel-stream");
- evtSource.onmessage = function(e) {
- document.getElementById("torrentTable").innerHTML = e.data;
- };
- </script>
- </body>
- </html>
- `)
- }
- func formatBytes(b int64) string {
- const unit = 1024
- if b < unit {
- return fmt.Sprintf("%d B", b)
- }
- div, exp := int64(unit), 0
- for n := b / unit; n >= unit; n /= unit {
- div *= unit
- exp++
- }
- return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "KMGTPE"[exp])
- }
- func panelStream(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "text/event-stream")
- w.Header().Set("Cache-Control", "no-cache")
- w.Header().Set("Connection", "keep-alive")
- w.Header().Set("Access-Control-Allow-Origin", "*")
- w.Header().Set("Accept", "text/event-stream")
- var rows string
- for _, t := range client.Torrents() {
- st := t.Stats()
- id := t.InfoHash().HexString()
- var totalSize int64
- var remaining int64
- var percent float64
- downloaded := st.BytesRead.Int64()
- //done := true
- if t.Info() != nil {
- totalSize = t.Info().TotalLength()
- if downloaded > totalSize {
- downloaded = totalSize
- }
- remaining = totalSize - downloaded
- if remaining < 0 {
- remaining = 0
- }
- percent = float64(downloaded) / float64(totalSize) * 100
- if percent > 100 {
- percent = 100
- }
- }
- rows += fmt.Sprintf(
- "<tr><td>%s</td><td>%d</td><td>%s</td><td>%s</td><td>%.1f%%</td>"+
- "<td>"+
- "<form method='POST' action='/cancel' style='display:inline;'>"+
- "<input type='hidden' name='id' value='%s'>"+
- "<button type='submit'>Cancel</button>"+
- "</form>"+
- "<form method='POST' action='/pause' style='display:inline;'>"+
- "<input type='hidden' name='id' value='%s'>"+
- "<button type='submit'>Pause</button>"+
- "</form>"+
- "<form method='POST' action='/resume' style='display:inline;'>"+
- "<input type='hidden' name='id' value='%s'>"+
- "<button type='submit'>Resume</button>"+
- "</form>"+
- "</td></tr>",
- t.Name(),
- st.ActivePeers,
- formatBytes(st.BytesRead.Int64()),
- formatBytes(remaining),
- percent,
- id, id, id)
- }
- fmt.Fprintf(w, "data: %s\n\n", rows)
- }
- func cancelHandler(w http.ResponseWriter, r *http.Request) {
- doesCookieExist(w, r)
- id := r.FormValue("id")
- t := findTorrentByID(id)
- if t != nil {
- t.Drop()
- fmt.Printf("%s cancelled", t.Name())
- }
- http.Redirect(w, r, "/panel", http.StatusSeeOther)
- }
- func pauseHandler(w http.ResponseWriter, r *http.Request) {
- doesCookieExist(w, r)
- id := r.FormValue("id")
- t := findTorrentByID(id)
- if t != nil {
- for _, f := range t.Files() {
- f.SetPriority(torrent.PiecePriorityNone)
- }
- }
- http.Redirect(w, r, "/panel", http.StatusSeeOther)
- }
- func findTorrentByID(id string) *torrent.Torrent {
- for _, t := range client.Torrents() {
- if t.InfoHash().HexString() == id {
- return t
- }
- }
- return nil
- }
- func resumeHandler(w http.ResponseWriter, r *http.Request) {
- doesCookieExist(w, r)
- id := r.FormValue("id")
- t := findTorrentByID(id)
- if t != nil {
- for _, f := range t.Files() {
- f.SetPriority(torrent.PiecePriorityNormal)
- }
- }
- http.Redirect(w, r, "/panel", http.StatusSeeOther)
- }
- func directoryList(w http.ResponseWriter, r *http.Request) {
- doesCookieExist(w, r)
- clean := strings.TrimPrefix(downloadfolder, "./")
- result := "/" + clean
- cleaned := strings.TrimPrefix(r.URL.Path, result)
- dirPath := filepath.Join(root, cleaned)
- rel, err := filepath.Rel(root, dirPath)
- if err != nil || strings.HasPrefix(rel, "..") {
- http.Error(w, "Invalid path", http.StatusBadRequest)
- return
- }
- info, err := os.Stat(dirPath)
- if err != nil {
- http.Error(w, "Not found", http.StatusNotFound)
- return
- }
- if info.IsDir() {
- // List directory contents
- files, err := os.ReadDir(dirPath)
- if err != nil {
- http.Error(w, "Cannot read directory!", http.StatusInternalServerError)
- return
- }
- fmt.Fprintf(w, `<style>
- body {
- font-family: Arial, Helvetica, sans-serif;
- color: black;
- }
- li {
- margin-right: 20px;
- }
- </style>
- <body>
- <h1>Listing of %s</h1><ul>`, r.URL.Path)
- parent := path.Dir(cleaned)
- if cleaned != "/" {
- fmt.Fprintf(w, `<li><a href="%s">[parent directory]</a></li>`, parent)
- }
- for _, f := range files {
- name := f.Name()
- link := path.Join(r.URL.Path, name)
- if f.IsDir() {
- fmt.Fprintf(w, "<li>[directory] <a href=\"%s/\">%s</a></li>", link, name)
- } else {
- fmt.Fprintf(w, "<li><a href=\"%s\" download>%s</a></li>", link, name)
- }
- }
- fmt.Fprint(w, "</ul>")
- fmt.Fprint(w, "</body>")
- } else {
- /*
- old code, this and the code block below is commented out as somehow when trying to
- download the file it would inject the CSS and probably HTML into the file contents
- messing up the headers and even changing the download extension
- to .htm, if it can be fixed, please try fixing it so this problem doesnt happen
- otherwise keep it commented
- */
- //data, err := os.ReadFile(dirPath)
- //if err != nil {
- // http.Error(w, "Cannot read file", http.StatusInternalServerError)
- // return
- //}
- /*
- fmt.Fprintf(w, `<style>
- body { font-family: Arial, sans-serif; background:#000; color:#0f0; }
- pre { white-space: pre-wrap; }
- </style><body>`)
- fmt.Fprintf(w, "<h1>View %s</h1><pre>%s</pre>", r.URL.Path, data)
- fmt.Fprint(w, "</body>")
- */
- http.ServeFile(w, r, dirPath)
- }
- }
- /*
- func userManager(w http.ResponseWriter, r *http.Request) {
- }
- */
- func ftpServer() {
- factory := &filedriver.FileDriverFactory{
- RootPath: downloadfolder,
- Perm: server.NewSimplePerm("user", "group"),
- }
- opts := &server.ServerOpts{
- Name: "Pixalianet FTP server",
- WelcomeMessage: "Welcome to the Pixalianet FTP panel",
- Factory: factory,
- Port: ftpport,
- Auth: &server.SimpleAuth{
- Name: username,
- Password: password,
- },
- }
- if usetls == true {
- opts.TLS = true
- opts.CertFile = tlscert
- opts.KeyFile = tlskey
- }
- fserver := server.NewServer(opts)
- fserver.ListenAndServe()
- }
- func parseConfig(data string) (string, string, string, string, int, error) {
- /*
- data, err := os.ReadFile("config.json")
- if err != nil {
- return "", "", "", 0, err
- }
- */
- type ftpconfig struct {
- Port int `json:"port"`
- }
- type tlsconfig struct {
- IsEnabled bool `json:"isEnabled"`
- CertFile string `json:"cert_file"`
- KeyFile string `json:"key_file"`
- }
- type config struct {
- Username string `json:"username"`
- Password string `json:"password"`
- Downloadfolder string `json:"downloadfolder"`
- Webport string `json:"webport"`
- Ftpconfig ftpconfig `json:"ftp"`
- Tlsconfig tlsconfig `json:"tls"`
- }
- var cfg config
- err := json.Unmarshal([]byte(data), &cfg)
- if err != nil {
- return "", "", "", "", 0, err
- }
- /*
- here we are applying the values we got than keep returning them since
- its so much easier to assign to a global var than add more parameters to the return
- from then on, lets just assign the decoded values to a global variable thatll be used
- */
- usetls = cfg.Tlsconfig.IsEnabled
- tlscert = cfg.Tlsconfig.CertFile
- tlskey = cfg.Tlsconfig.KeyFile
- return cfg.Username, cfg.Password, cfg.Downloadfolder, cfg.Webport, cfg.Ftpconfig.Port, nil
- }
- func cleanComments(data string) string {
- scanner := bufio.NewScanner(strings.NewReader(data))
- commentPrefix := "#"
- var builder strings.Builder
- for scanner.Scan() {
- s := scanner.Text()
- if idx := strings.Index(s, commentPrefix); idx != -1 {
- s = strings.TrimSpace(s[:idx])
- } else {
- s = strings.TrimSpace(s)
- }
- if s != "" {
- builder.WriteString(s)
- builder.WriteByte('\n')
- }
- }
- return builder.String()
- }
Add Comment
Please, Sign In to add comment