/ html / main.go
main.go
  1  package main
  2  
  3  import (
  4  	"context"
  5  	"fmt"
  6  	"html/template"
  7  	"log"
  8  	"os"
  9  	"sort"
 10  	"time"
 11  
 12  	"github.com/hekmon/cunits/v2"
 13  	"github.com/hyperreal64/transmissionrpc"
 14  )
 15  
 16  func byteCountIEC(b int64) string {
 17  	const unit = 1024
 18  	if b < unit {
 19  		return fmt.Sprintf("%d B", b)
 20  	}
 21  
 22  	div, exp := int64(unit), 0
 23  	for n := b / unit; n >= unit; n /= unit {
 24  		div *= unit
 25  		exp++
 26  	}
 27  
 28  	return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp])
 29  }
 30  
 31  func convertTime(seconds int64) string {
 32  	switch {
 33  	case seconds > 86400:
 34  		day := seconds / 86400
 35  		remainder := seconds % 86400
 36  		hour := remainder / 3600
 37  		remainder = remainder % 3600
 38  		minutes := remainder / 60
 39  		remainder = remainder % 60
 40  
 41  		return fmt.Sprintf("%d days, %d hours, %d minutes, %d seconds", day, hour, minutes, remainder)
 42  
 43  	case seconds < 86400 && seconds > 3600:
 44  		hour := seconds / 3600
 45  		remainder := seconds % 3600
 46  		minutes := remainder / 60
 47  		remainder = remainder % 60
 48  
 49  		return fmt.Sprintf("%d hours, %d minutes, %d seconds", hour, minutes, remainder)
 50  
 51  	case seconds < 3600 && seconds > 60:
 52  		minutes := seconds / 60
 53  		remainder := seconds % 60
 54  
 55  		return fmt.Sprintf("%d minutes, %d seconds", minutes, remainder)
 56  
 57  	default:
 58  		return fmt.Sprintf("%d seconds", seconds)
 59  	}
 60  
 61  }
 62  
 63  type SessionStat struct {
 64  	Label string
 65  	Value string
 66  }
 67  
 68  type TorrentInfo struct {
 69  	Name         string
 70  	ActivityDate time.Time
 71  	TotalSize    cunits.Bits
 72  	Leechers     int64
 73  	Seeders      int64
 74  }
 75  
 76  type TorrentStatsPageData struct {
 77  	Date            string
 78  	SessionStats    []SessionStat
 79  	CurrentStats    []SessionStat
 80  	CumulativeStats []SessionStat
 81  	TorrentInfo     []TorrentInfo
 82  }
 83  
 84  func main() {
 85  	transmissionbt, err := transmissionrpc.New(os.Getenv("TRANSMISSION_RPC_URL"), os.Getenv("TRANSMISSION_RPC_USER"), os.Getenv("TRANSMISSION_RPC_PASSWORD"), nil)
 86  	if err != nil {
 87  		log.Fatalln(err)
 88  	}
 89  
 90  	stats, err := transmissionbt.SessionStats(context.TODO())
 91  	if err != nil {
 92  		log.Fatalln(err)
 93  	}
 94  
 95  	sessionStats := []SessionStat{
 96  		{Label: "Active torrent count", Value: fmt.Sprintf("%d", stats.ActiveTorrentCount)},
 97  		{Label: "Download speed", Value: fmt.Sprintf("%s/sec", byteCountIEC(stats.DownloadSpeed))},
 98  		{Label: "Upload speed", Value: fmt.Sprintf("%s/sec", byteCountIEC(stats.UploadSpeed))},
 99  		{Label: "Paused torrent count", Value: fmt.Sprintf("%d", stats.PausedTorrentCount)},
100  		{Label: "Torrent count", Value: fmt.Sprintf("%d", stats.TorrentCount)},
101  	}
102  
103  	currentStats := []SessionStat{
104  		{Label: "Uploaded bytes", Value: fmt.Sprintf("%s", byteCountIEC(stats.CurrentStats.UploadedBytes))},
105  		{Label: "Downloaded bytes", Value: fmt.Sprintf("%s", byteCountIEC(stats.CurrentStats.DownloadedBytes))},
106  		{Label: "Files added", Value: fmt.Sprintf("%d", stats.CurrentStats.FilesAdded)},
107  		{Label: "Session count", Value: fmt.Sprintf("%d", stats.CurrentStats.SessionCount)},
108  		{Label: "Time active", Value: convertTime(stats.CurrentStats.SecondsActive)},
109  	}
110  
111  	cumulativeStats := []SessionStat{
112  		{Label: "Uploaded bytes", Value: fmt.Sprintf("%s", byteCountIEC(stats.CumulativeStats.UploadedBytes))},
113  		{Label: "Downloaded bytes", Value: fmt.Sprintf("%s", byteCountIEC(stats.CumulativeStats.DownloadedBytes))},
114  		{Label: "Files added", Value: fmt.Sprintf("%d", stats.CumulativeStats.FilesAdded)},
115  		{Label: "Session count", Value: fmt.Sprintf("%d", stats.CumulativeStats.SessionCount)},
116  		{Label: "Time active", Value: convertTime(stats.CumulativeStats.SecondsActive)},
117  	}
118  
119  	var torrentInfo = []TorrentInfo{}
120  	var (
121  		leecherCount int64
122  		seederCount  int64
123  	)
124  
125  	torrents, err := transmissionbt.TorrentGet(context.TODO(), []string{"name", "activityDate", "totalSize", "trackerStats"}, nil)
126  	if err != nil {
127  		fmt.Fprintln(os.Stderr, err)
128  	} else {
129  		for _, torrent := range torrents {
130  			for _, stat := range torrent.TrackerStats {
131  				leecherCount = stat.LeecherCount
132  				seederCount = stat.SeederCount
133  			}
134  			torrentInfo = append(torrentInfo, TorrentInfo{
135  				Name:         *torrent.Name,
136  				ActivityDate: *torrent.ActivityDate,
137  				TotalSize:    *torrent.TotalSize,
138  				Leechers:     leecherCount,
139  				Seeders:      seederCount,
140  			})
141  		}
142  	}
143  
144  	sort.Slice(torrentInfo, func(i, j int) bool {
145  		return torrentInfo[i].Name < torrentInfo[j].Name
146  	})
147  
148  	data := TorrentStatsPageData{
149  		Date:            time.Now().Format(time.UnixDate),
150  		SessionStats:    sessionStats,
151  		CurrentStats:    currentStats,
152  		CumulativeStats: cumulativeStats,
153  		TorrentInfo:     torrentInfo,
154  	}
155  
156  	htmlTemplate := `<!DOCTYPE html>
157  <html>
158  <head>
159    <title>Torrent Stats - hyperreal.coffee</title>
160    <link href="style.css" rel="stylesheet">
161  </head>
162  <style>
163    body {
164        max-width: 96%;
165    }
166  </style>
167  <body>
168  <div class="container">
169  <h1>Torrent Stats</h1>
170  <p>As of {{.Date}}</p>
171  
172  <h2>Session Stats</h2>
173  <table>
174    {{range .SessionStats}}
175    <tr>
176      <td>{{.Label}}</td>
177      <td>{{.Value}}</td>
178    </tr>
179    {{end}}
180  </table>
181  
182  <h2>Current Stats</h2>
183  <table>
184    {{range .CurrentStats}}
185    <tr>
186      <td>{{.Label}}</td>
187      <td>{{.Value}}</td>
188    </tr>
189    {{end}}
190  </table>
191  
192  <h2>Cumulative Stats</h2>
193  <table>
194    {{range .CumulativeStats}}
195    <tr>
196      <td>{{.Label}}</td>
197      <td>{{.Value}}</td>
198    </tr>
199    {{end}}
200  </table>
201  
202  <h2>Torrent Info</h2>
203  <table>
204    <tr>
205      <th>Name</th>
206      <th>Activity Date</th>
207      <th>Total Size</th>
208      <th>Leechers</th>
209      <th>Seeders</th>
210    </tr>
211    {{range .TorrentInfo}}
212    <tr>
213      <td>{{.Name}}</td>
214      <td>{{.ActivityDate}}</td>
215      <td>{{.TotalSize}}</td>
216      <td>{{.Leechers}}</td>
217      <td>{{.Seeders}}</td>
218    </tr>
219    {{end}}
220  </table>
221  </div>
222  </body>
223  </html>
224  `
225  
226  	html, err := template.New("torrentstats").Parse(htmlTemplate)
227  	if err != nil {
228  		log.Fatalln(err)
229  	}
230  
231  	htmlOutputFile, err := os.Create(os.Getenv("HTML_FILE"))
232  	if err != nil {
233  		log.Fatalln(err)
234  	}
235  	defer htmlOutputFile.Close()
236  
237  	if err = html.Execute(htmlOutputFile, data); err != nil {
238  		log.Fatalln(err)
239  	}
240  }