From 66001f861fd07f981905e5dffffefe05c75bd1b8 Mon Sep 17 00:00:00 2001 From: Mahmoud Rahbar Azad Date: Mon, 22 Oct 2018 23:55:29 +0200 Subject: [PATCH] =?UTF-8?q?Initial=20commit.=20Example=20output:=20Got=205?= =?UTF-8?q?80=20offers.=20Filtered=20offers:=203=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20ID|=20=20=20=20=20Ram|=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20HDD|=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20CPU|=20=20=20=20Price|=20?= =?UTF-8?q?=20=20Score|=20=20Reduce=20time|Specials=20=20=20SB57-931121|?= =?UTF-8?q?=20=20128=20GB|=20=202x=202=20TB=20(4096)|=20=20Intel=20Xeon=20?= =?UTF-8?q?E5-1650V2=20(12518)|=20=2057.00=20=E2=82=AC|=20=20103.12|=20=20?= =?UTF-8?q?=20=20=20=2053h=2053m|ECC,=20Ent.=20HDD,=20iNIC=20=20=20SB69-92?= =?UTF-8?q?7780|=20=20128=20GB|=20=202x=202=20TB=20(4096)|=20=20Intel=20Xe?= =?UTF-8?q?on=20E5-1650V3=20(13335)|=20=2069.00=20=E2=82=AC|=20=20=2089.92?= =?UTF-8?q?|=20=20=20=20=20=2049h=2039m|ECC,=20Ent.=20HDD,=20iNIC=20=20=20?= =?UTF-8?q?SB76-910394|=20=20128=20GB|=20=203x=202=20TB=20(6144)|=20=20Int?= =?UTF-8?q?el=20Xeon=20E5-1650V2=20(12518)|=20=2076.00=20=E2=82=AC|=20=20?= =?UTF-8?q?=2082.73|=20=20=20=20=20=2001h=2021m|ECC,=20Ent.=20HDD,=20iNIC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/client.go | 29 ++++++++++++++ crawler/crawler.go | 84 ++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + hetzner/endpoint.go | 12 ++++++ hetzner/types.go | 74 +++++++++++++++++++++++++++++++++++ main.go | 92 ++++++++++++++++++++++++++++++++++++++++++++ notifier/notifier.go | 1 + 7 files changed, 293 insertions(+) create mode 100644 client/client.go create mode 100644 crawler/crawler.go create mode 100644 go.mod create mode 100644 hetzner/endpoint.go create mode 100644 hetzner/types.go create mode 100644 main.go create mode 100644 notifier/notifier.go diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..46b22e4 --- /dev/null +++ b/client/client.go @@ -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) +} diff --git a/crawler/crawler.go b/crawler/crawler.go new file mode 100644 index 0000000..6ffe9e4 --- /dev/null +++ b/crawler/crawler.go @@ -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 +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ec28d39 --- /dev/null +++ b/go.mod @@ -0,0 +1 @@ +module github.com/mrahbar/my-bloody-hetzner-sb-notifier diff --git a/hetzner/endpoint.go b/hetzner/endpoint.go new file mode 100644 index 0000000..b332aae --- /dev/null +++ b/hetzner/endpoint.go @@ -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()) +} diff --git a/hetzner/types.go b/hetzner/types.go new file mode 100644 index 0000000..15f6c24 --- /dev/null +++ b/hetzner/types.go @@ -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) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..c3a64d0 --- /dev/null +++ b/main.go @@ -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.") + } +} diff --git a/notifier/notifier.go b/notifier/notifier.go new file mode 100644 index 0000000..ed45f23 --- /dev/null +++ b/notifier/notifier.go @@ -0,0 +1 @@ +package notifier