diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..849ddff --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..a65edfe --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,45 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy + # you may remove this if you don't need go generate + - go generate ./... +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + +archives: + - format: tar.gz + # this name template makes the OS and Arch compatible with the results of uname. + name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + # use zip for windows archives + format_overrides: + - goos: windows + format: zip +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ incpatch .Version }}-next" +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' + +# The lines beneath this are called `modelines`. See `:help modeline` +# Feel free to remove those if you don't want/use them. +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..8bd77f0 --- /dev/null +++ b/config/config.go @@ -0,0 +1,42 @@ +package config + +import ( + "encoding/json" + "os" +) + +type Config struct { + LogLevel string `json:"log_level"` + DiscordID string `json:"discord_id"` + DiscordToken string `json:"discord_token"` + Tokens []struct { + Token string `json:"token"` + Price float64 `json:"price"` + IsBelow bool `json:"is_below"` + IsAbove bool `json:"is_above"` + } `json:"tokens"` +} + +func GetConfig() (Config, error) { + var config Config + err := ReadJSON("config.json", &config) + if err != nil { + return config, err + } + return config, nil +} + +func ReadJSON(path string, v interface{}) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + decoder := json.NewDecoder(file) + err = decoder.Decode(&v) + if err != nil { + return err + } + return nil +} diff --git a/example.config.json b/example.config.json new file mode 100644 index 0000000..4b458ee --- /dev/null +++ b/example.config.json @@ -0,0 +1,13 @@ +{ + "log_level": "DEBUG", + "discord_id": "", + "discord_token": "", + "tokens": [ + { + "token": "WAGMI", + "price": 0.000001, + "is_below": false, + "is_above": true + } + ] +} \ No newline at end of file diff --git a/exrond/getPairsQuery.go b/exrond/getPairsQuery.go new file mode 100644 index 0000000..271a0ed --- /dev/null +++ b/exrond/getPairsQuery.go @@ -0,0 +1,75 @@ +package exrond + +import ( + "encoding/json" + "io" + "net/http" + "strings" + + "github.com/rs/zerolog/log" +) + +type Pairs struct { + Data struct { + Pairs []struct { + Address string `json:"address"` + FirstTokenPrice string `json:"firstTokenPrice"` + SecondTokenPrice string `json:"secondTokenPrice"` + FirstTokenReserve string `json:"firstTokenReserve"` + SecondTokenReserve string `json:"secondTokenReserve"` + FirstToken struct { + Identifier string `json:"identifier"` + Name string `json:"name"` + Decimals int `json:"decimals"` + Supply string `json:"supply"` + Ticker string `json:"ticker"` + Balance string `json:"balance"` + } `json:"firstToken"` + SecondToken struct { + Identifier string `json:"identifier"` + Name string `json:"name"` + Decimals int `json:"decimals"` + Supply string `json:"supply"` + Ticker string `json:"ticker"` + Balance string `json:"balance"` + } `json:"secondToken"` + } `json:"pairs"` + } `json:"data"` +} + +func GetPairsQuery() (Pairs, error) { + //Encode the data + var ( + url = "https://api.exrond.com/graphql" + ) + + request := ` + {"operationName":"GET_PAIRS_QUERY","variables":{},"query":"query GET_PAIRS_QUERY {\n pairs {\n address\n firstTokenPrice\n secondTokenPrice\n firstTokenReserve\n secondTokenReserve\n firstToken {\n identifier\n name\n decimals\n supply\n ticker\n balance\n assets {\n svgUrl\n pngUrl\n __typename\n }\n __typename\n }\n secondToken {\n identifier\n name\n decimals\n supply\n ticker\n balance\n assets {\n svgUrl\n pngUrl\n __typename\n }\n __typename\n }\n __typename\n }\n}"} + ` + + // Create a new request using http + resp, err := http.Post(url, "application/json", strings.NewReader(request)) + if err != nil { + log.Err(err).Msg("error making request") + return Pairs{}, err + } + + defer func() { + _ = resp.Body.Close() + }() + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Err(err).Msg("error reading response body") + return Pairs{}, err + } + + var p Pairs + err = json.Unmarshal(body, &p) + if err != nil { + log.Err(err).Msg("error unmarshalling data") + return Pairs{}, err + } + + return p, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..da7b4cc --- /dev/null +++ b/go.mod @@ -0,0 +1,19 @@ +module github.com/enzofoucaud/exrond-notifier + +go 1.20 + +require ( + github.com/disgoorg/disgo v0.16.5 + github.com/disgoorg/snowflake/v2 v2.0.1 + github.com/rs/zerolog v1.29.1 +) + +require ( + github.com/disgoorg/json v1.1.0 // indirect + github.com/disgoorg/log v1.2.0 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b // indirect + golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8 // indirect + golang.org/x/sys v0.1.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..be33cde --- /dev/null +++ b/go.sum @@ -0,0 +1,30 @@ +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/disgoorg/disgo v0.16.5 h1:6f2k9OwiDvLL/xq9mxevBVEpL0oSKYxM7aZ+v8mnt5w= +github.com/disgoorg/disgo v0.16.5/go.mod h1:wo61ZLPn6bxHVdUODjyZ3fZTnCT7giD3uknsDUwMGn8= +github.com/disgoorg/json v1.1.0 h1:7xigHvomlVA9PQw9bMGO02PHGJJPqvX5AnwlYg/Tnys= +github.com/disgoorg/json v1.1.0/go.mod h1:BHDwdde0rpQFDVsRLKhma6Y7fTbQKub/zdGO5O9NqqA= +github.com/disgoorg/log v1.2.0 h1:sqlXnu/ZKAlIlHV9IO+dbMto7/hCQ474vlIdMWk8QKo= +github.com/disgoorg/log v1.2.0/go.mod h1:3x1KDG6DI1CE2pDwi3qlwT3wlXpeHW/5rVay+1qDqOo= +github.com/disgoorg/snowflake/v2 v2.0.1 h1:CuUxGLwggUxEswZOmZ+mZ5i0xSumQdXW9tXW7uGqe+0= +github.com/disgoorg/snowflake/v2 v2.0.1/go.mod h1:SPU9c2CNn5DSyb86QcKtdZgix9osEtKrHLW4rMhfLCs= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= +github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= +github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b h1:qYTY2tN72LhgDj2rtWG+LI6TXFl2ygFQQ4YezfVaGQE= +github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8 h1:Xt4/LzbTwfocTk9ZLEu4onjeFucl88iW+v4j4PWbQuE= +golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/main.go b/main.go new file mode 100644 index 0000000..aeb2bf0 --- /dev/null +++ b/main.go @@ -0,0 +1,99 @@ +package main + +import ( + "context" + "os" + "strconv" + "time" + + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/webhook" + "github.com/disgoorg/snowflake/v2" + "github.com/enzofoucaud/exrond-notifier/config" + "github.com/enzofoucaud/exrond-notifier/exrond" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func init() { + // LOG + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) +} + +func main() { + log.Info().Msg("Starting bot") + + c, err := config.GetConfig() + if err != nil { + log.Err(err).Msg("error getting config") + return + } + + switch c.LogLevel { + case "INFO": + zerolog.SetGlobalLevel(zerolog.InfoLevel) + case "DEBUG": + zerolog.SetGlobalLevel(zerolog.DebugLevel) + default: + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } + + for { + pairs, err := exrond.GetPairsQuery() + if err != nil { + log.Err(err).Msg("error getting pairs") + return + } + + for _, pair := range pairs.Data.Pairs { + log.Debug().Msg("--------------") + log.Debug().Msg("Checking pair " + pair.FirstToken.Name + " " + pair.SecondToken.Name) + log.Debug().Msg("First token price: " + pair.FirstTokenPrice) + log.Debug().Msg("Second token price: " + pair.SecondTokenPrice) + // Check if pair is WAGMI + for _, token := range c.Tokens { + if token.Token == pair.SecondToken.Name { + if token.IsBelow { + pairFloat, _ := strconv.ParseFloat(pair.SecondTokenPrice, 64) + if pairFloat <= token.Price { + message := "WAGMI alert: token is below " + pair.SecondTokenPrice + err := Discord(message, c.DiscordID, c.DiscordToken) + if err != nil { + log.Err(err).Msg("error sending discord notification") + } + log.Info().Msg("Notifier sent for " + pair.FirstToken.Name + " " + pair.SecondToken.Name) + } + } + if token.IsAbove { + pairFloat, _ := strconv.ParseFloat(pair.SecondTokenPrice, 64) + if pairFloat >= token.Price { + message := "WAGMI alert: token is above " + pair.SecondTokenPrice + err := Discord(message, c.DiscordID, c.DiscordToken) + if err != nil { + log.Err(err).Msg("error sending discord notification") + } + log.Info().Msg("Notifier sent for " + pair.FirstToken.Name + " " + pair.SecondToken.Name) + } + } + } + } + } + log.Debug().Msg("--------------") + + time.Sleep(1 * time.Minute) + } +} + +func Discord(message, discordID, discordToken string) error { + client := webhook.New(snowflake.MustParse(discordID), discordToken) + defer client.Close(context.TODO()) + + if _, err := client.CreateMessage(discord.NewWebhookMessageCreateBuilder(). + SetContent(message). + Build(), + ); err != nil { + return err + } + + return nil +}