diff --git a/main.go b/main.go index 6837a05..8a1fdae 100644 --- a/main.go +++ b/main.go @@ -37,6 +37,7 @@ import ( "github.com/velour/catbase/plugins/rpgORdie" "github.com/velour/catbase/plugins/rss" "github.com/velour/catbase/plugins/sisyphus" + "github.com/velour/catbase/plugins/stock" "github.com/velour/catbase/plugins/talker" "github.com/velour/catbase/plugins/tell" "github.com/velour/catbase/plugins/tldr" @@ -122,6 +123,7 @@ func main() { b.AddPlugin(couldashouldawoulda.New(b)) b.AddPlugin(nerdepedia.New(b)) b.AddPlugin(tldr.New(b)) + b.AddPlugin(stock.New(b)) b.AddPlugin(cli.New(b)) // catches anything left, will always return true b.AddPlugin(fact.New(b)) diff --git a/plugins/stock/stock.go b/plugins/stock/stock.go new file mode 100644 index 0000000..e85fdab --- /dev/null +++ b/plugins/stock/stock.go @@ -0,0 +1,94 @@ +package stock + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "regexp" + "strings" + + "github.com/rs/zerolog/log" + + "github.com/velour/catbase/bot" + "github.com/velour/catbase/bot/msg" +) + +type StockPlugin struct { + bot bot.Bot + apiKey string +} + +func New(b bot.Bot) *StockPlugin { + s := &StockPlugin{ + bot: b, + apiKey: b.Config().GetString("Stock.API_KEY", "0E1DP61SJ7GF81IE"), + } + b.Register(s, bot.Message, s.message) + b.Register(s, bot.Help, s.help) + return s +} + +type GlobalQuote struct { + Info StockInfo `json:"GlobalQuote"` +} + +type StockInfo struct { + Symbol string `json:"symbol"` + Open string `json:"open"` + High string `json:"high"` + Low string `json:"low"` + Price string `json:"price"` + Volume string `json:"volume"` + LatestTradingDay string `json:"latesttradingday"` + PreviousClose string `json:"previousclose"` + Change string `json:"change"` + ChangePercent string `json:"changepercent"` +} + +func (p *StockPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + if !message.Command { + return false + } + + tokens := strings.Fields(message.Body) + numTokens := len(tokens) + + if numTokens == 2 && strings.ToLower(tokens[0]) == "stock-price" { + query := fmt.Sprintf("https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=%s&apikey=%s", tokens[1], p.apiKey) + + resp, err := http.Get(query) + if err != nil { + log.Fatal().Err(err).Msg("Failed to get stock info") + } + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + log.Fatal().Err(err).Msg("Error stock info body") + } + + response := "Failed to retrieve data for stock symbol: " + tokens[1] + + cleaned := strings.ReplaceAll(string(body), " ", "") + regex := regexp.MustCompile("[0-9][0-9]\\.") + + cleaned = regex.ReplaceAllString(cleaned, "") + + var info GlobalQuote + err = json.Unmarshal([]byte(cleaned), &info) + + if err == nil && strings.EqualFold(tokens[1], info.Info.Symbol) { + response = fmt.Sprintf("%s : $%s (%s)", tokens[1], info.Info.Price, info.Info.ChangePercent) + } + + p.bot.Send(c, bot.Message, message.Channel, response) + return true + } + + return false +} + +func (p *StockPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.bot.Send(c, bot.Message, message.Channel, "try '!stock-price SYMBOL'") + return true +} diff --git a/plugins/stock/stock_test.go b/plugins/stock/stock_test.go new file mode 100644 index 0000000..914f6d2 --- /dev/null +++ b/plugins/stock/stock_test.go @@ -0,0 +1,45 @@ +package stock + +import ( + "github.com/velour/catbase/plugins/cli" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/velour/catbase/bot" + "github.com/velour/catbase/bot/msg" + "github.com/velour/catbase/bot/user" +) + +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { + isCmd := strings.HasPrefix(payload, "!") + if isCmd { + payload = payload[1:] + } + return &cli.CliPlugin{}, bot.Message, msg.Message{ + User: &user.User{Name: "tester"}, + Channel: "test", + Body: payload, + Command: isCmd, + } +} + +func TestValid(t *testing.T) { + mb := bot.NewMockBot() + c := New(mb) + assert.NotNil(t, c) + res := c.message(makeMessage("!stock-price TWTR")) + assert.Len(t, mb.Messages, 1) + assert.True(t, res) + assert.Contains(t, mb.Messages[0], "TWTR : $") +} + +func TestInvalid(t *testing.T) { + mb := bot.NewMockBot() + c := New(mb) + assert.NotNil(t, c) + res := c.message(makeMessage("!stock-price NOTREAL")) + assert.Len(t, mb.Messages, 1) + assert.True(t, res) + assert.Contains(t, mb.Messages[0], "Failed to retrieve data for stock symbol: NOTREAL") +}