From b2d01e7dd7a23baf369d95bdf4c166630fdf9078 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Thu, 14 Jan 2021 14:40:35 -0700 Subject: [PATCH] added currency conversions --- cmd/convert.go | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++ cmd/types.go | 24 +++++++++++++ main.go | 5 +++ 3 files changed, 123 insertions(+) create mode 100644 cmd/convert.go create mode 100644 cmd/types.go diff --git a/cmd/convert.go b/cmd/convert.go new file mode 100644 index 0000000..ac708a2 --- /dev/null +++ b/cmd/convert.go @@ -0,0 +1,94 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strconv" + "strings" + "unicode" + + "github.com/kf5grd/keybasebot" + "github.com/teris-io/shortid" + "samhofi.us/x/keybase/v2/types/chat1" +) + +var ConvertAd = chat1.UserBotCommandInput{ + Name: "convert", + Usage: " to ", + Description: "Converts between many crypto and fiat currencies. Try !convert 50 USD to XLM", +} + +func Convert(m chat1.MsgSummary, b *keybasebot.Bot) (bool, error) { + fields := strings.Fields(strings.TrimSpace(strings.Replace(m.Content.Text.Body, "!convert", "", 1))) + + // first pass filters + if fields == nil || len(fields) != 4 || fields[2] != "to" { + return true, fmt.Errorf("@%s - Invalid Request.", m.Sender.Username) + } + + // get the arguments + lhq := fields[0] + lhc := strings.ToUpper(fields[1]) + rhc := strings.ToUpper(fields[3]) + + // validate the args + leftQuantity, err := strconv.ParseFloat(lhq, 64) + if err != nil { + return true, fmt.Errorf("@%s - Unable to parse %s into a number", m.Sender.Username, lhq) + } + if !isAlpha(lhc) || !isAlpha(rhc) { + return true, fmt.Errorf("@%s - Only letters are allowed in currency symbols", m.Sender.Username) + } + rate, err := getConversion(lhc, rhc) + if err != nil { + eid := shortid.MustGenerate() + b.Logger.Error("%s: %+v", eid, err) + b.KB.ReactByConvID(m.ConvID, m.Id, "Error: %s", eid) + return true, nil + } + rhq := leftQuantity * rate + b.KB.ReactByConvID(m.ConvID, m.Id, fmt.Sprintf("%.4f %s", rhq, rhc)) + return true, nil +} + +func getConversion(lhc string, rhc string) (float64, error) { + client := &http.Client{} + target := fmt.Sprintf("https://api.cryptonator.com/api/ticker/%s-%s", lhc, rhc) + req, err := http.NewRequest("GET", target, nil) + if err != nil { + return 0.0, err + } + req.Header.Set("User-Agent", "GoLang Currency Bot ssh0le/0.99") + resp, err := client.Do(req) + if err != nil { + return 0.0, err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return 0.0, err + } + var apiResponse CryptApiResponse + if err := json.Unmarshal(body, &apiResponse); err != nil { + return 0.0, err + } + if !apiResponse.Success { + return 0.0, fmt.Errorf(apiResponse.Error) + } + result, err := strconv.ParseFloat(apiResponse.Ticker.Price, 64) + if err != nil { + return 0.0, err + } + return result, nil +} + +func isAlpha(s string) bool { + for _, letter := range s { + if !unicode.IsLetter(letter) { + return false + } + } + return true +} diff --git a/cmd/types.go b/cmd/types.go new file mode 100644 index 0000000..504726a --- /dev/null +++ b/cmd/types.go @@ -0,0 +1,24 @@ +package cmd + +// CryptApiResponse hold a response for currency conversions, used in !convert +type CryptApiResponse struct { + Ticker CryptonatorRate `json:"ticker"` + Timestamp int `json:"timestamp"` + Success bool `json:"success"` + Error string `json:"error"` +} + +type CryptonatorRate struct { + Base string `json:"base"` + Target string `json:"target"` + Price string `json:"price"` + Volume string `json:"volume"` + Change string `json:"change"` + Markets []CryptonatorMarket `json:"markets,omitempty"` +} + +type CryptonatorMarket struct { + Market string `json:"market"` + Price string `json:"price"` + Volume float64 `json:"volume"` +} diff --git a/main.go b/main.go index 0aa837d..3e2945e 100644 --- a/main.go +++ b/main.go @@ -55,6 +55,11 @@ func main() { Ad: &cmd.AgeAd, Run: keybasebot.Adapt(cmd.Age, keybasebot.MessageType("text"), keybasebot.CommandPrefix("!age")), }, + keybasebot.BotCommand{ + Name: "convert", + Ad: &cmd.ConvertAd, + Run: keybasebot.Adapt(cmd.Convert, keybasebot.MessageType("text"), keybasebot.CommandPrefix("!convert")), + }, ) // catch ctrl-c so we can clean up c := make(chan os.Signal)