mpldr

main.go

Apr 25th, 2026
278
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Go 17.13 KB | None | 0 0
  1. package main
  2.  
  3. //import "C"
  4. import (
  5.     "bufio"
  6.     "crypto/rand"
  7.     "encoding/json"
  8.     "log"
  9.  
  10.     //"crypto/tls"
  11.     "encoding/hex"
  12.     "fmt"
  13.     "net/http"
  14.     "os"
  15.     "path"
  16.     "path/filepath"
  17.     "strings"
  18.     "time"
  19.  
  20.     //"time"
  21.  
  22.     "github.com/anacrolix/torrent"
  23.     filedriver "github.com/goftp/file-driver"
  24.     "github.com/goftp/server"
  25. )
  26.  
  27. var (
  28.     username       string
  29.     downloadfolder string
  30.     ftpport        int
  31.     password       string
  32.     webport        string
  33.     root           string
  34.     usetls         bool
  35.     tlscert        string
  36.     tlskey         string
  37.     client         *torrent.Client
  38.     sessions       = map[string]time.Time{}
  39. )
  40.  
  41. func main() {
  42.     data, err1 := os.ReadFile("config.json")
  43.     if err1 != nil {
  44.         log.Fatal(err1)
  45.     }
  46.     cleaned := cleanComments(string(data))
  47.     var errs error
  48.     username, password, downloadfolder, webport, ftpport, errs = parseConfig(cleaned)
  49.     if errs != nil {
  50.         panic(errs)
  51.     }
  52.     if usetls == true && tlscert == "" || tlskey == "" {
  53.         log.Fatal("You must set tls cert! and tls key!")
  54.     }
  55.     root, _ = filepath.Abs(downloadfolder)
  56.     cfg := torrent.NewDefaultClientConfig()
  57.     /*
  58.             if errd != nil {
  59.                 log.Fatal(errd)
  60.             }
  61.     */
  62.     cfg.DataDir = downloadfolder
  63.     /*
  64.         conf := torrent.ClientConfig{
  65.             DataDir: downloadfolder,
  66.             Debug:   false,
  67.         }
  68.         //cfg.HTTPUserAgent = "Mozilla 5.0"
  69.  
  70.     */
  71.     var err error
  72.     client, err = torrent.NewClient(cfg)
  73.     if err != nil {
  74.         panic(err)
  75.     }
  76.     /*
  77.         if err != nil {
  78.             panic(err)
  79.         }
  80.     */
  81.     /*
  82.         reader := bufio.NewReader(os.Stdin)
  83.         fmt.pri
  84.  
  85.     */
  86.     defer client.Close()
  87.     magnets := make(chan string)
  88.     http.HandleFunc("/gettorrent", func(w http.ResponseWriter, r *http.Request) {
  89.         doesCookieExist(w, r)
  90.         m := r.FormValue("addtorrent")
  91.         if m != "" {
  92.             magnets <- m
  93.             http.Redirect(w, r, "/panel", http.StatusFound)
  94.         } else {
  95.             http.Error(w, "No magnet provided", http.StatusBadRequest)
  96.         }
  97.         http.Redirect(w, r, "/", http.StatusSeeOther)
  98.  
  99.     })
  100.     //torrents := []*torrent.Torrent{}
  101.     go func() {
  102.         for m := range magnets {
  103.             t, err := client.AddMagnet(m)
  104.             if err != nil {
  105.                 fmt.Println("Error adding magnet:", err)
  106.                 continue
  107.             }
  108.             go monitorTorrent(t)
  109.         }
  110.     }()
  111.     go func() { // launch a tiny goroutine to periodically clean stale cookies
  112.         for {
  113.             //now := time.Now()
  114.             for id, exp := range sessions {
  115.                 now := time.Now()
  116.                 if now.After(exp) {
  117.                     delete(sessions, id)
  118.                 }
  119.             }
  120.             time.Sleep(40 * time.Second)
  121.         }
  122.     }()
  123.     /*
  124.         sig := make(chan os.Signal, 1)
  125.         signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
  126.         <-sig
  127.         fm
  128.     */
  129.     http.HandleFunc("/", homeHandler)
  130.     http.HandleFunc("/login", loginHandler)
  131.     http.HandleFunc("/panel", managementPanel)
  132.     http.HandleFunc("/panel-stream", panelStream)
  133.     http.HandleFunc("/cancel", cancelHandler)
  134.     http.HandleFunc("/pause", pauseHandler)
  135.     http.HandleFunc("/resume", resumeHandler)
  136.     http.HandleFunc("/downloads/", directoryList)
  137.     fmt.Println("http://localhost:8080")
  138.     go ftpServer()
  139.     if usetls == true {
  140.         err = http.ListenAndServeTLS(webport, tlscert, tlskey, nil)
  141.     } else {
  142.         err = http.ListenAndServe(webport, nil)
  143.     }
  144.     if err != nil {
  145.         panic(err)
  146.     }
  147. }
  148. func monitorTorrent(t *torrent.Torrent) {
  149.     <-t.GotInfo()
  150.     t.DownloadAll()
  151.     fmt.Println("Started downloading:", t.Name())
  152.  
  153.     ticker := time.NewTicker(5 * time.Second)
  154.     defer ticker.Stop()
  155.  
  156.     for {
  157.         select {
  158.         case <-ticker.C:
  159.             st := t.Stats()
  160.             fmt.Printf("[%s] Peers: %d, Downloaded: %d bytes\n",
  161.                 t.Name(), st.ActivePeers, st.BytesRead)
  162.  
  163.             // Check if all files are complete
  164.             done := true
  165.             for _, f := range t.Files() {
  166.                 if f.BytesCompleted() < f.Length() {
  167.                     done = false
  168.                     break
  169.                 }
  170.             }
  171.             if done {
  172.                 fmt.Printf("Torrent %s is complete!\n", t.Name())
  173.                 t.Drop()
  174.                 return
  175.             }
  176.  
  177.         case <-t.Closed():
  178.             fmt.Printf("Stopped monitoring %s\n", t.Name())
  179.             return
  180.         }
  181.     }
  182. }
  183. func doesCookieExist(w http.ResponseWriter, r *http.Request) {
  184.     cookie, err := r.Cookie("session")
  185.     if err != nil {
  186.         http.Redirect(w, r, "/", http.StatusSeeOther)
  187.         return
  188.     }
  189.     sessionID := cookie.Value
  190.     expiry, ok := sessions[sessionID]
  191.  
  192.     if !ok || time.Now().After(expiry) {
  193.         http.Redirect(w, r, "/", http.StatusSeeOther)
  194.         return
  195.     }
  196. }
  197. func homeHandler(w http.ResponseWriter, r *http.Request) {
  198.     //fmt.Println(r.RemoteAddr, "connected") // meh useless..alerts only neccessary for logins
  199.     fmt.Fprint(w, `
  200. <!DOCTYPE html>
  201. <html lang="en">
  202. <head>
  203.     <meta charset="UTF-8">
  204.     <title>Login</title>
  205. </head>
  206. <style>
  207.     body {
  208.         font-family: "Segoe UI", Arial, sans-serif;
  209.         background-color: #121212;
  210.         color: #e0e0e0;
  211.         margin: 0;
  212.         padding: 0;
  213.     }
  214.     nav {
  215.         background: #1f1f1f;
  216.         padding: 12px 20px;
  217.         box-shadow: 0 2px 4px rgba(0,0,0,0.6);
  218.     }
  219.     nav a {
  220.         color: #ffffff;
  221.         margin-right: 20px;
  222.         text-decoration: none;
  223.         font-weight: 500;
  224.         font-size: 16px;
  225.     }
  226.     .mm {
  227.         background: #1f1f1f;
  228.         padding: 12px 20px;
  229.         box-shadow: 0 2px 4px rgba(0,0,0,0.6);
  230.     }
  231.     nav a:hover {
  232.         color: #4dabf7;
  233.     }
  234.     h1, h2 {
  235.         margin: 20px;
  236.         color: #ffffff;
  237.     }
  238.     form {
  239.         margin: 20px;
  240.     }
  241.     label {
  242.         margin-right: 15px;
  243.     }
  244.     input[type="submit"] {
  245.         background: #4dabf7;
  246.         border: none;
  247.         padding: 6px 12px;
  248.         color: #fff;
  249.         cursor: pointer;
  250.         border-radius: 4px;
  251.     }
  252.     input[type="submit"]:hover {
  253.         background: #339af0;
  254.     }
  255.     input[type="text"] {
  256.         padding: 8px;
  257.         margin: 20px;
  258.         width: 300px;
  259.         border: 1px solid #444;
  260.         border-radius: 4px;
  261.         background: #1e1e1e;
  262.         color: #e0e0e0;
  263.     }
  264.     table {
  265.         width: 95%;
  266.         margin: 20px auto;
  267.         border-collapse: collapse;
  268.         background: #1e1e1e;
  269.         border-radius: 6px;
  270.         overflow: hidden;
  271.     }
  272.     th {
  273.         background: #2c2c2c;
  274.         color: #ffffff;
  275.         text-align: left;
  276.         padding: 10px;
  277.     }
  278.     td {
  279.         padding: 10px;
  280.         border-bottom: 1px solid #333;
  281.     }
  282.     tr:hover {
  283.         background-color: #2a2a2a;
  284.     }
  285.     a {
  286.         color: #4dabf7;
  287.     }
  288.     a:hover {
  289.         text-decoration: underline;
  290.     }
  291.     .pre {
  292.         background: #1e1e1e;
  293.         padding: 15px;
  294.         border: 1px solid #333;
  295.         border-radius: 6px;
  296.         overflow-x: auto;
  297.         margin: 20px;
  298.         font-family: Consolas, monospace;
  299.     }
  300.     .m {
  301.         font: bold 14px/1.5 "Lucida Grande", Helvetica, Arial, sans-serif;
  302.         text-anchor: middle;
  303.     }
  304. </style>
  305. <body>
  306. <h1 class="mm">Login</h1>
  307. <form method="POST" class="mm" action="/login">
  308.     <label class="m">User:</label><input name="username"><br>
  309.     <label class="m">Pass:</label><input type="password" name="password"><br>
  310.     <input type="submit" class="pre" value="Login">
  311. </form>
  312. </body>
  313. </html>
  314.     `)
  315. }
  316. func loginHandler(w http.ResponseWriter, r *http.Request) {
  317.     if r.Method != http.MethodPost {
  318.         http.Redirect(w, r, "/", http.StatusSeeOther)
  319.         return
  320.     }
  321.  
  322.     user := r.FormValue("username")
  323.     pass := r.FormValue("password")
  324.  
  325.     if user == username && pass == password {
  326.         sessionID := generateSessionID()
  327.         expiry := time.Now().Add(16 * time.Minute)
  328.  
  329.         // Store the cookie in the map
  330.         sessions[sessionID] = expiry
  331.         fmt.Println("login attempted", r.RemoteAddr, r.Host) // notifies if someone logged in. also works as intrusion detection in case
  332.         http.SetCookie(w, &http.Cookie{
  333.             Name:     "session",
  334.             Value:    sessionID,
  335.             Path:     "/",
  336.             Expires:  expiry,
  337.             SameSite: http.SameSiteLaxMode,
  338.         })
  339.  
  340.         http.Redirect(w, r, "/panel", http.StatusSeeOther)
  341.     } else {
  342.         fmt.Fprint(w, "<h1>Incorrect username or password try again</h1>")
  343.         http.Redirect(w, r, "/", http.StatusSeeOther)
  344.     }
  345. }
  346. func generateSessionID() string {
  347.     b := make([]byte, 16)
  348.     rand.Read(b)
  349.     return hex.EncodeToString(b)
  350. }
  351. func managementPanel(w http.ResponseWriter, r *http.Request) {
  352.     doesCookieExist(w, r)
  353.  
  354.     fmt.Fprint(w, `<!DOCTYPE html>
  355. <html>
  356. <head>
  357.     <title>Administrator</title>
  358.     <style>
  359.         body {
  360.             font-family: "Segoe UI", Arial, sans-serif;
  361.             background-color: #121212;
  362.             color: #e0e0e0;
  363.             margin: 0;
  364.             padding: 0;
  365.         }
  366.         table { border-collapse: collapse; width: 90%; margin: 20px auto; }
  367.         th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
  368.         th { background: #0078d7; color: #fff; }
  369.         tr:nth-child(even) { background: #f9f9f9; }
  370.         form { margin: 20px auto; width: 80%; }
  371.         input[type=text] { width: 70%; padding: 6px; }
  372.         button { padding: 6px 12px; background:#0078d7; color:#fff; border:none; }
  373.         h1, h2 {
  374.             margin: 20px;
  375.             color: #ffffff;
  376.         }
  377.         input[type="submit"] {
  378.             background: #4dabf7;
  379.             border: none;
  380.             padding: 6px 12px;
  381.             color: #fff;
  382.             cursor: pointer;
  383.             border-radius: 4px;
  384.         }
  385.         input[type="submit"]:hover {
  386.             background: #339af0;
  387.         }
  388.         input[type="text"] {
  389.             padding: 8px;
  390.             margin: 20px;
  391.             width: 300px;
  392.             border: 1px solid #444;
  393.             border-radius: 4px;
  394.             background: #1e1e1e;
  395.             color: #e0e0e0;
  396.         }
  397.         table {
  398.             width: 95%;
  399.             margin: 20px auto;
  400.             border-collapse: collapse;
  401.             background: #1e1e1e;
  402.             border-radius: 6px;
  403.             overflow: hidden;
  404.         }
  405.     </style>
  406. </head>
  407. <body>
  408. <h1 style="text-align:center;">Active Torrents</h1>
  409.  
  410. <form method="POST" action="/gettorrent">
  411.     <input type="text" name="addtorrent" placeholder="Paste magnet link here">
  412.     <button type="submit">Add Torrent</button>
  413. </form>
  414.  
  415. <table>
  416.     <thead>
  417.     <tr>
  418.         <th>Name</th>
  419.         <th>Peers</th>
  420.         <th>Downloaded</th>
  421.         <th>Remaining</th>
  422.         <th>Progress</th>
  423.         <th>Actions</th>
  424.     </tr>
  425.     </thead>
  426.     <tbody id="torrentTable">
  427.     </tbody>
  428. </table>
  429.  
  430. <script>
  431.     const evtSource = new EventSource("/panel-stream");
  432.     evtSource.onmessage = function(e) {
  433.         document.getElementById("torrentTable").innerHTML = e.data;
  434.     };
  435. </script>
  436. </body>
  437. </html>
  438.     `)
  439. }
  440. func formatBytes(b int64) string {
  441.     const unit = 1024
  442.     if b < unit {
  443.         return fmt.Sprintf("%d B", b)
  444.     }
  445.     div, exp := int64(unit), 0
  446.     for n := b / unit; n >= unit; n /= unit {
  447.         div *= unit
  448.         exp++
  449.     }
  450.     return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "KMGTPE"[exp])
  451. }
  452. func panelStream(w http.ResponseWriter, r *http.Request) {
  453.     w.Header().Set("Content-Type", "text/event-stream")
  454.     w.Header().Set("Cache-Control", "no-cache")
  455.     w.Header().Set("Connection", "keep-alive")
  456.     w.Header().Set("Access-Control-Allow-Origin", "*")
  457.     w.Header().Set("Accept", "text/event-stream")
  458.     var rows string
  459.     for _, t := range client.Torrents() {
  460.         st := t.Stats()
  461.         id := t.InfoHash().HexString()
  462.  
  463.         var totalSize int64
  464.         var remaining int64
  465.         var percent float64
  466.         downloaded := st.BytesRead.Int64()
  467.         //done := true
  468.         if t.Info() != nil {
  469.             totalSize = t.Info().TotalLength()
  470.             if downloaded > totalSize {
  471.                 downloaded = totalSize
  472.             }
  473.             remaining = totalSize - downloaded
  474.             if remaining < 0 {
  475.                 remaining = 0
  476.             }
  477.             percent = float64(downloaded) / float64(totalSize) * 100
  478.             if percent > 100 {
  479.                 percent = 100
  480.             }
  481.         }
  482.         rows += fmt.Sprintf(
  483.             "<tr><td>%s</td><td>%d</td><td>%s</td><td>%s</td><td>%.1f%%</td>"+
  484.                 "<td>"+
  485.                 "<form method='POST' action='/cancel' style='display:inline;'>"+
  486.                 "<input type='hidden' name='id' value='%s'>"+
  487.                 "<button type='submit'>Cancel</button>"+
  488.                 "</form>"+
  489.                 "<form method='POST' action='/pause' style='display:inline;'>"+
  490.                 "<input type='hidden' name='id' value='%s'>"+
  491.                 "<button type='submit'>Pause</button>"+
  492.                 "</form>"+
  493.                 "<form method='POST' action='/resume' style='display:inline;'>"+
  494.                 "<input type='hidden' name='id' value='%s'>"+
  495.                 "<button type='submit'>Resume</button>"+
  496.                 "</form>"+
  497.                 "</td></tr>",
  498.             t.Name(),
  499.             st.ActivePeers,
  500.             formatBytes(st.BytesRead.Int64()),
  501.             formatBytes(remaining),
  502.             percent,
  503.             id, id, id)
  504.     }
  505.  
  506.     fmt.Fprintf(w, "data: %s\n\n", rows)
  507. }
  508.  
  509. func cancelHandler(w http.ResponseWriter, r *http.Request) {
  510.     doesCookieExist(w, r)
  511.     id := r.FormValue("id")
  512.     t := findTorrentByID(id)
  513.     if t != nil {
  514.         t.Drop()
  515.         fmt.Printf("%s cancelled", t.Name())
  516.     }
  517.     http.Redirect(w, r, "/panel", http.StatusSeeOther)
  518. }
  519. func pauseHandler(w http.ResponseWriter, r *http.Request) {
  520.     doesCookieExist(w, r)
  521.     id := r.FormValue("id")
  522.     t := findTorrentByID(id)
  523.     if t != nil {
  524.         for _, f := range t.Files() {
  525.             f.SetPriority(torrent.PiecePriorityNone)
  526.         }
  527.     }
  528.     http.Redirect(w, r, "/panel", http.StatusSeeOther)
  529. }
  530. func findTorrentByID(id string) *torrent.Torrent {
  531.     for _, t := range client.Torrents() {
  532.         if t.InfoHash().HexString() == id {
  533.             return t
  534.         }
  535.     }
  536.     return nil
  537. }
  538. func resumeHandler(w http.ResponseWriter, r *http.Request) {
  539.     doesCookieExist(w, r)
  540.     id := r.FormValue("id")
  541.  
  542.     t := findTorrentByID(id)
  543.     if t != nil {
  544.         for _, f := range t.Files() {
  545.             f.SetPriority(torrent.PiecePriorityNormal)
  546.         }
  547.     }
  548.  
  549.     http.Redirect(w, r, "/panel", http.StatusSeeOther)
  550. }
  551. func directoryList(w http.ResponseWriter, r *http.Request) {
  552.     doesCookieExist(w, r)
  553.     clean := strings.TrimPrefix(downloadfolder, "./")
  554.     result := "/" + clean
  555.     cleaned := strings.TrimPrefix(r.URL.Path, result)
  556.     dirPath := filepath.Join(root, cleaned)
  557.  
  558.     rel, err := filepath.Rel(root, dirPath)
  559.     if err != nil || strings.HasPrefix(rel, "..") {
  560.         http.Error(w, "Invalid path", http.StatusBadRequest)
  561.         return
  562.     }
  563.     info, err := os.Stat(dirPath)
  564.     if err != nil {
  565.         http.Error(w, "Not found", http.StatusNotFound)
  566.         return
  567.     }
  568.     if info.IsDir() {
  569.         // List directory contents
  570.         files, err := os.ReadDir(dirPath)
  571.         if err != nil {
  572.             http.Error(w, "Cannot read directory!", http.StatusInternalServerError)
  573.             return
  574.         }
  575.         fmt.Fprintf(w, `<style>
  576.     body {
  577.         font-family: Arial, Helvetica, sans-serif;
  578.         color: black;
  579.     }
  580.     li {
  581.         margin-right: 20px;
  582.     }
  583.  
  584. </style>
  585. <body>
  586. <h1>Listing of %s</h1><ul>`, r.URL.Path)
  587.         parent := path.Dir(cleaned)
  588.         if cleaned != "/" {
  589.             fmt.Fprintf(w, `<li><a href="%s">[parent directory]</a></li>`, parent)
  590.         }
  591.  
  592.         for _, f := range files {
  593.             name := f.Name()
  594.             link := path.Join(r.URL.Path, name)
  595.             if f.IsDir() {
  596.                 fmt.Fprintf(w, "<li>[directory] <a href=\"%s/\">%s</a></li>", link, name)
  597.             } else {
  598.                 fmt.Fprintf(w, "<li><a href=\"%s\" download>%s</a></li>", link, name)
  599.             }
  600.         }
  601.         fmt.Fprint(w, "</ul>")
  602.         fmt.Fprint(w, "</body>")
  603.     } else {
  604.         /*
  605.             old code, this and the code block below is commented out as somehow when trying to
  606.             download the file it would inject the CSS and probably HTML into the file contents
  607.             messing up the headers and even changing the download extension
  608.             to .htm, if it can be fixed, please try fixing it so this problem doesnt happen
  609.             otherwise keep it commented
  610.         */
  611.         //data, err := os.ReadFile(dirPath)
  612.         //if err != nil {
  613.         //  http.Error(w, "Cannot read file", http.StatusInternalServerError)
  614.         //  return
  615.         //}
  616.         /*
  617.                         fmt.Fprintf(w, `<style>
  618.                 body { font-family: Arial, sans-serif; background:#000; color:#0f0; }
  619.                 pre { white-space: pre-wrap; }
  620.             </style><body>`)
  621.  
  622.                         fmt.Fprintf(w, "<h1>View %s</h1><pre>%s</pre>", r.URL.Path, data)
  623.                         fmt.Fprint(w, "</body>")
  624.         */
  625.         http.ServeFile(w, r, dirPath)
  626.  
  627.     }
  628. }
  629.  
  630. /*
  631. func userManager(w http.ResponseWriter, r *http.Request) {
  632.  
  633. }
  634. */
  635. func ftpServer() {
  636.     factory := &filedriver.FileDriverFactory{
  637.         RootPath: downloadfolder,
  638.         Perm:     server.NewSimplePerm("user", "group"),
  639.     }
  640.  
  641.     opts := &server.ServerOpts{
  642.         Name:           "Pixalianet FTP server",
  643.         WelcomeMessage: "Welcome to the Pixalianet FTP panel",
  644.         Factory:        factory,
  645.         Port:           ftpport,
  646.         Auth: &server.SimpleAuth{
  647.             Name:     username,
  648.             Password: password,
  649.         },
  650.     }
  651.     if usetls == true {
  652.         opts.TLS = true
  653.         opts.CertFile = tlscert
  654.         opts.KeyFile = tlskey
  655.     }
  656.     fserver := server.NewServer(opts)
  657.     fserver.ListenAndServe()
  658. }
  659. func parseConfig(data string) (string, string, string, string, int, error) {
  660.     /*
  661.         data, err := os.ReadFile("config.json")
  662.         if err != nil {
  663.             return "", "", "", 0, err
  664.         }
  665.  
  666.     */
  667.     type ftpconfig struct {
  668.         Port int `json:"port"`
  669.     }
  670.     type tlsconfig struct {
  671.         IsEnabled bool   `json:"isEnabled"`
  672.         CertFile  string `json:"cert_file"`
  673.         KeyFile   string `json:"key_file"`
  674.     }
  675.     type config struct {
  676.         Username       string    `json:"username"`
  677.         Password       string    `json:"password"`
  678.         Downloadfolder string    `json:"downloadfolder"`
  679.         Webport        string    `json:"webport"`
  680.         Ftpconfig      ftpconfig `json:"ftp"`
  681.         Tlsconfig      tlsconfig `json:"tls"`
  682.     }
  683.     var cfg config
  684.     err := json.Unmarshal([]byte(data), &cfg)
  685.     if err != nil {
  686.         return "", "", "", "", 0, err
  687.     }
  688.     /*
  689.             here we are applying the values we got than keep returning them since
  690.             its so much easier to assign to a global var than add more parameters to the return
  691.         from then on, lets just assign the decoded values to a global variable thatll be used
  692.     */
  693.     usetls = cfg.Tlsconfig.IsEnabled
  694.     tlscert = cfg.Tlsconfig.CertFile
  695.     tlskey = cfg.Tlsconfig.KeyFile
  696.     return cfg.Username, cfg.Password, cfg.Downloadfolder, cfg.Webport, cfg.Ftpconfig.Port, nil
  697. }
  698. func cleanComments(data string) string {
  699.     scanner := bufio.NewScanner(strings.NewReader(data))
  700.     commentPrefix := "#"
  701.     var builder strings.Builder
  702.     for scanner.Scan() {
  703.         s := scanner.Text()
  704.         if idx := strings.Index(s, commentPrefix); idx != -1 {
  705.             s = strings.TrimSpace(s[:idx])
  706.         } else {
  707.             s = strings.TrimSpace(s)
  708.         }
  709.         if s != "" {
  710.             builder.WriteString(s)
  711.             builder.WriteByte('\n')
  712.         }
  713.     }
  714.     return builder.String()
  715. }
  716.  
Add Comment
Please, Sign In to add comment