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 }