Initial commit.
Example output: Got 580 offers. Filtered offers: 3 ID| Ram| HDD| CPU| Price| Score| Reduce time|Specials SB57-931121| 128 GB| 2x 2 TB (4096)| Intel Xeon E5-1650V2 (12518)| 57.00 €| 103.12| 53h 53m|ECC, Ent. HDD, iNIC SB69-927780| 128 GB| 2x 2 TB (4096)| Intel Xeon E5-1650V3 (13335)| 69.00 €| 89.92| 49h 39m|ECC, Ent. HDD, iNIC SB76-910394| 128 GB| 3x 2 TB (6144)| Intel Xeon E5-1650V2 (12518)| 76.00 €| 82.73| 01h 21m|ECC, Ent. HDD, iNIC
This commit is contained in:
commit
66001f861f
7 changed files with 293 additions and 0 deletions
29
client/client.go
Normal file
29
client/client.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
func NewClient() *Client {
|
||||
crawler := &Client{
|
||||
&http.Client{Timeout: 10 * time.Second} ,
|
||||
}
|
||||
|
||||
return crawler
|
||||
}
|
||||
|
||||
func (c *Client) DoRequest(url string, target interface{}) error {
|
||||
r, err := c.httpClient.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
return json.NewDecoder(r.Body).Decode(target)
|
||||
}
|
84
crawler/crawler.go
Normal file
84
crawler/crawler.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package crawler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mrahbar/my-bloody-hetzner-sb-notifier/hetzner"
|
||||
"os"
|
||||
"sort"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
type Crawler struct {
|
||||
tabWriter *tabwriter.Writer
|
||||
|
||||
minPrice float64
|
||||
maxPrice float64
|
||||
|
||||
minRam int
|
||||
maxRam int
|
||||
|
||||
minHddSize int
|
||||
maxHddSize int
|
||||
|
||||
minHddCount int
|
||||
maxHddCount int
|
||||
|
||||
minBenchmark int
|
||||
maxBenchmark int
|
||||
}
|
||||
|
||||
func NewCrawler(minPrice float64, maxPrice float64, minRam int, maxRam int, minHddSize int, maxHddSize int, minHddCount int, maxHddCount int, minBenchmark int, maxBenchmark int) *Crawler {
|
||||
crawler := &Crawler{
|
||||
tabwriter.NewWriter(os.Stdout, 0, 8, 2, ' ', tabwriter.Debug|tabwriter.AlignRight),
|
||||
minPrice,
|
||||
maxPrice,
|
||||
minRam,
|
||||
maxRam,
|
||||
minHddSize,
|
||||
maxHddSize,
|
||||
minHddCount,
|
||||
maxHddCount,
|
||||
minBenchmark,
|
||||
maxBenchmark,
|
||||
}
|
||||
|
||||
return crawler
|
||||
}
|
||||
|
||||
func (c *Crawler) Filter(servers []hetzner.Server) []hetzner.Server {
|
||||
var filteredServers []hetzner.Server
|
||||
for _, server := range servers {
|
||||
if !c.isFiltered(server) {
|
||||
filteredServers = append(filteredServers, server)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(servers, func(i, j int) bool {
|
||||
return servers[i].Score() > servers[j].Score()
|
||||
})
|
||||
return filteredServers
|
||||
}
|
||||
|
||||
func (c *Crawler) Print(servers []hetzner.Server) {
|
||||
fmt.Fprintf(c.tabWriter, "%s\n", servers[0].Header())
|
||||
for _, server := range servers {
|
||||
fmt.Fprintf(c.tabWriter, "%s\n", server.ToString())
|
||||
}
|
||||
c.tabWriter.Flush()
|
||||
}
|
||||
|
||||
|
||||
func (c *Crawler) isFiltered(server hetzner.Server) bool {
|
||||
filtered := true
|
||||
|
||||
priceParsed := server.ParsePrice()
|
||||
if server.Cpu_benchmark >= c.minBenchmark && server.Cpu_benchmark <= c.maxBenchmark &&
|
||||
priceParsed >= c.minPrice && priceParsed <= c.maxPrice &&
|
||||
server.Ram >= c.minRam && server.Ram <= c.maxRam &&
|
||||
server.TotalHdd() >= c.minHddSize && server.TotalHdd() <= c.maxHddSize &&
|
||||
server.Hdd_count >= c.minHddCount && server.Hdd_count <= c.maxHddCount {
|
||||
filtered = false
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
1
go.mod
Normal file
1
go.mod
Normal file
|
@ -0,0 +1 @@
|
|||
module github.com/mrahbar/my-bloody-hetzner-sb-notifier
|
12
hetzner/endpoint.go
Normal file
12
hetzner/endpoint.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package hetzner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const hetznerlivedataurl = "https://www.hetzner.de/a_hz_serverboerse/live_data.json"
|
||||
|
||||
func MakeUrl() string {
|
||||
return fmt.Sprintf("%s?m=%v", hetznerlivedataurl, time.Now().UnixNano())
|
||||
}
|
74
hetzner/types.go
Normal file
74
hetzner/types.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package hetzner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Offers struct {
|
||||
Server []Server `json:"server"`
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Key int `json:"key"`
|
||||
Name string `json:"name"`
|
||||
Freetext string `json:"freetext"`
|
||||
Description []string `json:"description"`
|
||||
Dist []string `json:"dist"`
|
||||
Datacenter []string `json:"datacenter"`
|
||||
Specials []string `json:"specials"`
|
||||
Traffic string `json:"traffic"`
|
||||
Bandwith int `json:"bandwith"`
|
||||
|
||||
Price string `json:"price"`
|
||||
Price_v string `json:"price_v"`
|
||||
Setup_price string `json:"setup_price"`
|
||||
|
||||
Cpu string `json:"cpu"`
|
||||
Cpu_benchmark int `json:"cpu_benchmark"`
|
||||
Cpu_count int `json:"cpu_count"`
|
||||
Ram int `json:"ram"`
|
||||
Ram_hr string `json:"ram_hr"`
|
||||
Hdd_size int `json:"hdd_size"`
|
||||
Hdd_hr string `json:"hdd_hr"`
|
||||
Hdd_count int `json:"hdd_count"`
|
||||
SpecialHdd string `json:"specialHdd"`
|
||||
|
||||
Next_reduce int `json:"next_reduce"`
|
||||
Next_reduce_hr string `json:"next_reduce_hr"`
|
||||
|
||||
Fixed_price bool `json:"fixed_price"`
|
||||
Is_highio bool `json:"is_highio"`
|
||||
Is_ecc bool `json:"is_ecc"`
|
||||
}
|
||||
|
||||
func (s *Server) TotalHdd() int {
|
||||
return s.Hdd_count*s.Hdd_size
|
||||
}
|
||||
|
||||
func (s *Server) Score() float64 {
|
||||
return (float64(s.TotalHdd())*0.2 + float64(s.Ram)*0.4 + float64(s.Cpu_benchmark)*0.4)/s.ParsePrice()
|
||||
}
|
||||
|
||||
func (s *Server) ParsePrice() float64 {
|
||||
priceParsed, err := strconv.ParseFloat(s.Price, 32)
|
||||
if err != nil {
|
||||
fmt.Printf("Could not parse price %s for server %s: %s", s.Price, s.Key, err)
|
||||
return -1
|
||||
}
|
||||
return priceParsed*1.19
|
||||
}
|
||||
|
||||
func (s *Server) Header() string {
|
||||
return fmt.Sprint("ID\tRam\tHDD\tCPU\tPrice\tScore\tReduce time\tSpecials")
|
||||
}
|
||||
|
||||
func (s *Server) ToString() string {
|
||||
fixedPriceSymbol := "*"
|
||||
if !s.Fixed_price {
|
||||
fixedPriceSymbol = ""
|
||||
}
|
||||
specials := strings.Join(s.Specials, ", ")
|
||||
return fmt.Sprintf("%s-%d\t%s\t%s (%d)\t%s (%d)\t%.2f €%s\t%.2f\t%s\t%s", s.Name, s.Key, s.Ram_hr, s.Hdd_hr, s.TotalHdd(), s.Cpu, s.Cpu_benchmark, s.ParsePrice(), fixedPriceSymbol, s.Score(), s.Next_reduce_hr, specials)
|
||||
}
|
92
main.go
Normal file
92
main.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/mrahbar/my-bloody-hetzner-sb-notifier/client"
|
||||
c "github.com/mrahbar/my-bloody-hetzner-sb-notifier/crawler"
|
||||
"github.com/mrahbar/my-bloody-hetzner-sb-notifier/hetzner"
|
||||
)
|
||||
|
||||
var (
|
||||
minPrice = flag.Float64(
|
||||
"min-price",
|
||||
0,
|
||||
"set min price",
|
||||
)
|
||||
maxPrice = flag.Float64(
|
||||
"max-price",
|
||||
297,
|
||||
"set max price",
|
||||
)
|
||||
|
||||
minRam = flag.Int(
|
||||
"min-ram",
|
||||
0,
|
||||
"set min ram",
|
||||
)
|
||||
maxRam = flag.Int(
|
||||
"max-ram",
|
||||
256,
|
||||
"set max ram",
|
||||
)
|
||||
|
||||
minHddSize = flag.Int(
|
||||
"min-hdd-size",
|
||||
0,
|
||||
"set min hdd size",
|
||||
)
|
||||
maxHddSize = flag.Int(
|
||||
"max-hdd-size",
|
||||
6144,
|
||||
"set max hdd size",
|
||||
)
|
||||
|
||||
minHddCount = flag.Int(
|
||||
"min-hdd-count",
|
||||
0,
|
||||
"set min hdd count",
|
||||
)
|
||||
maxHddCount = flag.Int(
|
||||
"max-hdd-count",
|
||||
15,
|
||||
"set max hdd count",
|
||||
)
|
||||
|
||||
minBenchmark = flag.Int(
|
||||
"min-benchmark",
|
||||
0,
|
||||
"set min benchmark",
|
||||
)
|
||||
maxBenchmark = flag.Int(
|
||||
"max-benchmark",
|
||||
20000,
|
||||
"set max benchmark",
|
||||
)
|
||||
|
||||
alertOnScore = flag.Int(
|
||||
"alert-on-score",
|
||||
0,
|
||||
"set alert on score",
|
||||
)
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
offers := &hetzner.Offers{}
|
||||
err := client.NewClient().DoRequest(hetzner.MakeUrl(), offers)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to get hetzner live data: %s", err))
|
||||
}
|
||||
|
||||
if len(offers.Server) > 0 {
|
||||
crawler := c.NewCrawler(*minPrice, *maxPrice, *minRam, *maxRam, *minHddSize, *maxHddSize, *minHddCount, *maxHddCount, *minBenchmark, *maxBenchmark)
|
||||
servers := crawler.Filter(offers.Server)
|
||||
|
||||
fmt.Printf("Got %d offers. Filtered offers: %d\n", len(offers.Server), len(servers))
|
||||
crawler.Print(servers)
|
||||
} else {
|
||||
fmt.Println("Got no offers.")
|
||||
}
|
||||
}
|
1
notifier/notifier.go
Normal file
1
notifier/notifier.go
Normal file
|
@ -0,0 +1 @@
|
|||
package notifier
|
Reference in a new issue