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:
Mahmoud Rahbar Azad 2018-10-22 23:55:29 +02:00
commit 66001f861f
No known key found for this signature in database
GPG Key ID: 7DBBD39E2BFEB784
7 changed files with 293 additions and 0 deletions

29
client/client.go Normal file
View 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
View 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
View File

@ -0,0 +1 @@
module github.com/mrahbar/my-bloody-hetzner-sb-notifier

12
hetzner/endpoint.go Normal file
View 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
View 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
View 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
View File

@ -0,0 +1 @@
package notifier