diff options
| -rw-r--r-- | Dockerfile | 3 | ||||
| -rw-r--r-- | docker-compose.yaml | 17 | ||||
| -rw-r--r-- | exprparser.go | 207 | ||||
| -rw-r--r-- | go.mod | 10 | ||||
| -rw-r--r-- | go.sum | 91 | ||||
| -rw-r--r-- | hived.go | 248 | 
6 files changed, 330 insertions, 246 deletions
| @@ -2,7 +2,8 @@ FROM alpine:3.13 as builder  RUN apk update && apk upgrade  RUN apk add go git -RUN go get -u github.com/go-telegram-bot-api/telegram-bot-api +COPY ./go.* /hived/ +RUN cd /hived && go mod download  COPY *.go /hived/  RUN cd /hived && go build diff --git a/docker-compose.yaml b/docker-compose.yaml index 9495faf..4285550 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,4 +1,4 @@ -version: "3.8" +version: "3.4"  services:    hived:      image: hived @@ -12,6 +12,19 @@ services:      restart: unless-stopped      ports:        - "8008:8008" +    depends_on: +      - redis +  redis: +    image: redis:6.2-alpine +    networks: +      - hivednet +    restart: unless-stopped +    ports: +      - "6379:6379" +    environment: +      - ALLOW_EMPTY_PASSWORD=yes +    volumes: +      - redis-data:/data/  networks:    hivednet:  secrets: @@ -19,3 +32,5 @@ secrets:      file: ./tgtoken.json    ch_api_key:      file: ./changelly_api_key.json +volumes: +  redis-data: diff --git a/exprparser.go b/exprparser.go deleted file mode 100644 index 0d62dbc..0000000 --- a/exprparser.go +++ /dev/null @@ -1,207 +0,0 @@ -package main - -import ( -	"bufio" -	"bytes" -	"io" -) - -type Token int - -const eof = rune(0) - -const ( -	ILLEGAL Token = iota -	EOF -	WS - -	IDENT - -	ASTERISK -	COMMA -	SEMICOLON -	GT -	LT -	GET -	LET -	EQ -	PLUS -	MINUS -	DIVISION -	R_PAREN -	L_PAREN -	PERIOD -) - -type Scanner struct { -	r *bufio.Reader -} - -func newScanner(r io.Reader) *Scanner { -	return &Scanner{r: bufio.NewReader(r)} -} - -func (s *Scanner) read() rune { -	ch, _, err := s.r.ReadRune() -	if err != nil { -		return eof -	} - -	return ch -} - -func isWhiteSpace(ch rune) bool { -	if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' { -		return true -	} -	return false -} - -func isLetter(ch rune) bool { -	if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') { -		return true -	} -	return false -} - -func isDigit(ch rune) bool { -	if ch >= '0' && ch <= '9' { -		return true -	} -	return false -} - -func isNumber(chs []rune) bool { -	var dotCounter int -	for i := range chs { -		if chs[i] == '.' { -			dotCounter++ -			if dotCounter > 1 { -				return false -			} -			continue -		} - -		if !isDigit(chs[i]) { -			return false -		} -	} - -	return true -} - -func (s *Scanner) unread() { _ = s.r.UnreadRune() } - -func (s *Scanner) scanWhitespace() (tok Token, lit string) { -	var buf bytes.Buffer -	buf.WriteRune(s.read()) - -	for { -		if ch := s.read(); ch == eof { -			break -		} else if !isWhiteSpace(ch) { -			s.unread() -			break -		} else { -			buf.WriteRune(ch) -		} -	} - -	return WS, buf.String() -} - -func (s *Scanner) scanIdent() (tok Token, lit string) { -	var buf bytes.Buffer -	buf.WriteRune(s.read()) - -	for { -		if ch := s.read(); ch == eof { -			break -		} else if !isLetter(ch) && !isDigit(ch) && ch != '_' { -			s.unread() -			break -		} else { -			_, _ = buf.WriteRune(ch) -		} -	} - -	return IDENT, buf.String() -} - -func (s *Scanner) scan() (tok Token, lit string) { -	ch := s.read() - -	if isWhiteSpace(ch) { -		s.unread() -		return s.scanWhitespace() -	} else if isLetter(ch) { -		s.unread() -		return s.scanIdent() -	} - -	switch ch { -	case eof: -		return EOF, string(ch) -	case '*': -		return ASTERISK, string(ch) -	case '+': -		return PLUS, string(ch) -	case '-': -		return MINUS, string(ch) -	case '/': -		return ASTERISK, string(ch) -	case '(': -		return R_PAREN, string(ch) -	case ')': -		return L_PAREN, string(ch) -	case ',': -		return ASTERISK, string(ch) -	case ';': -		return ASTERISK, string(ch) -	case '<': -		return LT, string(ch) -	case '>': -		return GT, string(ch) -	case '.': -		return PERIOD, string(ch) -	} - -	return ILLEGAL, string(ch) -} - -type Parser struct { -	s   *Scanner -	buf struct { -		tok Token -		lit string -		n   int -	} -} - -func newParser(r io.Reader) *Parser { -	return &Parser{s: newScanner(r)} -} - -func (p *Parser) scan() (tok Token, lit string) { -	if p.buf.n != 0 { -		p.buf.n = 0 -		return p.buf.tok, p.buf.lit -	} - -	tok, lit = p.s.scan() -	p.buf.tok, p.buf.lit = tok, lit - -	return -} - -func (p *Parser) unscan() { p.buf.n = 1 } - -type ASTNode struct { -	isIdentifier bool -	isLiteral    bool -	isIllegal    bool -	isEOF        bool -	isWS         bool -	children     []*ASTNode -	value        interface{} -} @@ -0,0 +1,10 @@ +module github.com/terminaldweller/hived + +go 1.15 + +require ( +	github.com/Knetic/govaluate v3.0.0+incompatible +	github.com/go-redis/redis/v8 v8.6.0 +	github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible +	github.com/technoweenie/multipartstreamer v1.0.1 // indirect +) @@ -0,0 +1,91 @@ +github.com/Knetic/govaluate v1.5.0 h1:L4MyqdJSld9xr2eZcZHCWLfeIX2SBjqrwIKG1pcm/+4= +github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg= +github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis/v8 v8.6.0 h1:swqbqOrxaPztsj2Hf1p94M3YAgl7hYEpcw21z299hh8= +github.com/go-redis/redis/v8 v8.6.0/go.mod h1:DQ9q4Rk2HtwkrwVrdgmphoOQDMfpvcd/nHEwRsicg8s= +github.com/go-telegram-bot-api/telegram-bot-api v1.0.0 h1:HXVtsZ+yINQeyyhPFAUU4yKmeN+iFhJ87jXZOC016gs= +github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= +github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= +github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/otel v0.17.0 h1:6MKOu8WY4hmfpQ4oQn34u6rYhnf2sWf1LXYO/UFm71U= +go.opentelemetry.io/otel v0.17.0/go.mod h1:Oqtdxmf7UtEvL037ohlgnaYa1h7GtMh0NcSd9eqkC9s= +go.opentelemetry.io/otel/metric v0.17.0 h1:t+5EioN8YFXQ2EH+1j6FHCKMUj+57zIDSnSGr/mWuug= +go.opentelemetry.io/otel/metric v0.17.0/go.mod h1:hUz9lH1rNXyEwWAhIWCMFWKhYtpASgSnObJFnU26dJ0= +go.opentelemetry.io/otel/oteltest v0.17.0/go.mod h1:JT/LGFxPwpN+nlsTiinSYjdIx3hZIGqHCpChcIZmdoE= +go.opentelemetry.io/otel/trace v0.17.0 h1:SBOj64/GAOyWzs5F680yW1ITIfJkm6cJWL2YAvuL9xY= +go.opentelemetry.io/otel/trace v0.17.0/go.mod h1:bIujpqg6ZL6xUTubIUgziI1jSaUPthmabA/ygf/6Cfg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -1,6 +1,7 @@  package main  import ( +	"context"  	"encoding/json"  	"flag"  	"fmt" @@ -10,7 +11,10 @@ import (  	"net/url"  	"strconv"  	"sync" +	"time" +	"github.com/Knetic/govaluate" +	"github.com/go-redis/redis/v8"  	"github.com/go-telegram-bot-api/telegram-bot-api"  ) @@ -18,46 +22,71 @@ var flagPort = flag.String("port", "8008", "determined the port the sercice runs  var flagTgTokenFile = flag.String("tgtoken", "/run/secrets/tg_bot_token", "determines the location of the telegram bot token file")  var changelllyAPIKeyFile = flag.String("chapikey", "/run/secrets/ch_api_key", "determines the file that holds the changelly api key")  var alertFile = flag.String("alertfile", "/run/secrets/alerts", "determines the locaiton of the alert files") -var alertsCheckInterval = flag.Int64("alertinterval", 60., "in seconds, the amount of time between alert checks") +var alertsCheckInterval = flag.Int64("alertinterval", 600., "in seconds, the amount of time between alert checks") +var redisAddress = flag.String("redisaddress", "redis:6379", "determines the address of the redis instance") +var redisPassword = flag.String("redispassword", "", "determines the password of the redis db") +var redisDB = flag.Int64("redisdb", 0, "determines the db number")  const cryptocomparePriceURL = "https://min-api.cryptocompare.com/data/price?"  const changellyURL = "https://api.changelly.com" +const botChannelID = 146328407 -func getTgToken() string { +var getRedisClientOnce sync.Once +var getTGBotOnce sync.Once + +type TgToken struct { +	Token string `json:"token"` +} + +func getTGToken() string {  	tgTokenJsonBytes, err := ioutil.ReadFile(*flagTgTokenFile)  	if err != nil {  		log.Fatal(err)  	} -	type TgToken struct { -		Token string `json:"token"` -	} -  	var tgToken TgToken  	err = json.Unmarshal(tgTokenJsonBytes, &tgToken)  	if err != nil {  		log.Fatal(err)  	} -  	return tgToken.Token  } -func runTgBot() { -	botToken := getTgToken() -	bot, err := tgbotapi.NewBotAPI(botToken) -	if err != nil { -		log.Panic(err) -	} +func getTgBot() *tgbotapi.BotAPI { +	var tgbot *tgbotapi.BotAPI +	getTGBotOnce.Do(func() { +		tgTokenJsonBytes, err := ioutil.ReadFile(*flagTgTokenFile) +		if err != nil { +			log.Fatal(err) +		} + +		var tgToken TgToken + +		err = json.Unmarshal(tgTokenJsonBytes, &tgToken) +		if err != nil { +			log.Fatal(err) +		} + +		bot, err := tgbotapi.NewBotAPI(tgToken.Token) +		if err != nil { +			log.Panic(err) +		} -	bot.Debug = true +		bot.Debug = true +		tgbot = bot +	}) +	return tgbot +} +func runTgBot() { +	bot := getTgBot()  	log.Printf("Authorized on account %s", bot.Self.UserName) -	u := tgbotapi.NewUpdate(0) -	u.Timeout = 60 +	update := tgbotapi.NewUpdate(0) +	update.Timeout = 60 -	updates, err := bot.GetUpdatesChan(u) +	updates, err := bot.GetUpdatesChan(update)  	if err != nil {  		log.Panic(err)  	} @@ -122,10 +151,8 @@ func sendGetToCryptoCompare(  	fmt.Println(string(body)) -	//FIXME-blocks forever  	priceChan <- priceChanStruct{name: name, price: jsonBody[unit]}  	errChan <- errorChanStruct{hasError: false, err: nil} -	fmt.Println("done and done")  }  //TODO @@ -133,11 +160,6 @@ func healthHandler(w http.ResponseWriter, r *http.Request) {  }  func cryptoHandler(w http.ResponseWriter, r *http.Request) { -	if r.URL.Path != "/crypto" { -		http.Error(w, "404 not found.", http.StatusNotFound) -		return -	} -  	if r.Method != "GET" {  		http.Error(w, "Method is not supported.", http.StatusNotFound)  	} @@ -222,7 +244,6 @@ func pairHandler(w http.ResponseWriter, r *http.Request) {  	go sendGetToCryptoCompare(one, "USD", &wg, priceChan, errChan)  	go sendGetToCryptoCompare(two, "USD", &wg, priceChan, errChan)  	wg.Wait() -	fmt.Println("getting fucked here")  	for i := 0; i < 2; i++ {  		select { @@ -256,33 +277,186 @@ func pairHandler(w http.ResponseWriter, r *http.Request) {  	json.NewEncoder(w).Encode(map[string]interface{}{"ratio": ratio})  } -func getAlerts() map[string]interface{} { -	alertsBytes, err := ioutil.ReadFile(*flagTgTokenFile) -	if err != nil { -		log.Fatal(err) -		return make(map[string]interface{}) +type alertType struct { +	Name string `json:"name"` +	Expr string `json:"expr"` +} + +type alertsType struct { +	Alerts []alertType `json:"alerts"` +} + +func getRedisClient() *redis.Client { +	var client *redis.Client +	getRedisClientOnce.Do(func() { +		rdb := redis.NewClient(&redis.Options{ +			Addr:     *redisAddress, +			Password: *redisPassword, +			DB:       int(*redisDB), +		}) +		client = rdb +	}) + +	return client +} + +func getAlerts() (alertsType, error) { +	var alerts alertsType +	// rdb := getRedisClient() +	rdb := redis.NewClient(&redis.Options{ +		Addr:     *redisAddress, +		Password: *redisPassword, +		DB:       int(*redisDB), +	}) +	ctx := context.Background() +	keys := rdb.SMembersMap(ctx, "alertkeys") +	alerts.Alerts = make([]alertType, len(keys.Val())) +	vals := keys.Val() + +	i := 0 +	for key := range vals { +		alert := rdb.Get(ctx, key[6:]) +		expr, _ := alert.Result() +		alerts.Alerts[i].Name = key +		alerts.Alerts[i].Expr = expr +		i++  	} -	alertsJson := make(map[string]interface{}) +	return alerts, nil +} -	err = json.Unmarshal(alertsBytes, &alertsJson) +func getAlertsFromRedis() (alertsType, error) { +	// rdb := getRedisClient() +	rdb := redis.NewClient(&redis.Options{ +		Addr:     *redisAddress, +		Password: *redisPassword, +		DB:       int(*redisDB), +	}) +	ctx := context.Background() +	val, err := rdb.Get(ctx, "alert").Result()  	if err != nil { -		log.Fatal(err) -		return make(map[string]interface{}) +		log.Printf(err.Error()) +		return alertsType{}, err  	} +	fmt.Println(val) -	return alertsJson +	return alertsType{}, nil  }  func alertManager() { -	alerts := getAlerts() -	fmt.Println(alerts) +	for { +		alerts, err := getAlerts() +		if err != nil { +			log.Printf(err.Error()) +			return +		} +		fmt.Println(alerts) + +		for i := range alerts.Alerts { +			expression, err := govaluate.NewEvaluableExpression(alerts.Alerts[i].Expr) +			if err != nil { +				log.Printf(err.Error()) +				continue +			} + +			vars := expression.Vars() +			parameters := make(map[string]interface{}, len(vars)) + +			var wg sync.WaitGroup +			priceChan := make(chan priceChanStruct, len(vars)) +			errChan := make(chan errorChanStruct, len(vars)) +			defer close(priceChan) +			defer close(errChan) +			wg.Add(len(vars)) + +			for i := range vars { +				go sendGetToCryptoCompare(vars[i], "USD", &wg, priceChan, errChan) +			} +			wg.Wait() + +			for i := 0; i < len(vars); i++ { +				select { +				case err := <-errChan: +					if err.hasError != false { +						log.Printf(err.err.Error()) +					} +				default: +					log.Fatal("this shouldnt have happened") +				} +			} + +			for i := 0; i < len(vars); i++ { +				select { +				case price := <-priceChan: +					parameters[price.name] = price.price +				default: +					log.Fatal("this shouldnt have happened") +				} +			} + +			fmt.Println("parameters:", parameters) +			result, err := expression.Evaluate(parameters) +			if err != nil { +				log.Println(err.Error()) +			} + +			var resultBool bool +			fmt.Println("result:", result) +			resultBool = result.(bool) +			if resultBool == true { +				bot, err := tgbotapi.NewBotAPI(getTGToken()) +				if err != nil { +					log.Panic(err) +				} +				// bot := getTgBot() +				msgText := "notification " + alerts.Alerts[i].Expr + " has been triggered" +				msg := tgbotapi.NewMessage(botChannelID, msgText) +				bot.Send(msg) +			} +		} + +		time.Sleep(time.Second * time.Duration(*alertsCheckInterval)) +	} +} + +type addAlertJSONType struct { +	Name string `json:"name"` +	Expr string `json:"expr"` +} + +func addAlertHandler(w http.ResponseWriter, r *http.Request) { +	if r.Method != "POST" { +		http.Error(w, "Method is not supported.", http.StatusNotFound) +	} + +	bodyBytes, err := ioutil.ReadAll(r.Body) +	if err != nil { +		log.Printf(err.Error()) +	} + +	var bodyJSON addAlertJSONType +	json.Unmarshal(bodyBytes, &bodyJSON) +	fmt.Println(bodyJSON) + +	// rdb := getRedisClient() +	rdb := redis.NewClient(&redis.Options{ +		Addr:         *redisAddress, +		Password:     *redisPassword, +		DB:           int(*redisDB), +		MinIdleConns: 1, +	}) +	ctx := context.Background() +	key := "alert:" + bodyJSON.Name +	rdb.Set(ctx, bodyJSON.Name, bodyJSON.Expr, 0) +	rdb.SAdd(ctx, "alertkeys", key) +	json.NewEncoder(w).Encode(map[string]interface{}{"isSuccessful": true, "error": ""})  }  func startServer() {  	http.HandleFunc("/health", healthHandler)  	http.HandleFunc("/crypto", cryptoHandler)  	http.HandleFunc("/pair", pairHandler) +	http.HandleFunc("/addalert", addAlertHandler)  	if err := http.ListenAndServe(":"+*flagPort, nil); err != nil {  		log.Fatal(err) | 
