plugins/stocks.go (view raw)
1package plugins
2
3import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "net/http"
8 "strings"
9 "time"
10
11 "git.icyphox.sh/paprika/config"
12 "github.com/dustin/go-humanize"
13 "gopkg.in/irc.v3"
14)
15
16func init() {
17 Register(Stocks{})
18}
19
20type Stocks struct{}
21
22var (
23 stockClient = &http.Client{
24 Timeout: 10 * time.Second,
25 }
26 api_endpoint = "https://cloud.iexapis.com/v1"
27 NoIEXApi = errors.New("No IEX API key")
28)
29
30func (Stocks) Triggers() []string {
31 return []string{".stock", ".stonk"}
32}
33
34type tickerData struct {
35 Quote struct {
36 Symbol string `json:"symbol"`
37 Current float64 `json:"latestPrice"`
38 High float64 `json:"high,omitempty"`
39 Low float64 `json:"low,omitempty"`
40 ChangePercent float64 `json:"changePercent"`
41 } `json:"quote"`
42 Stats struct {
43 Company string `json:"companyName"`
44 Change1y float64 `json:"year1ChangePercent"`
45 Change6M float64 `json:"month6ChangePercent"`
46 Change30d float64 `json:"day30ChangePercent"`
47 Change5d float64 `json:"day5ChangePercent"`
48 } `json:"stats"`
49}
50
51func formatMoneyNum(field string, value float64, percent bool) string {
52 if percent {
53 v := humanize.CommafWithDigits(value*100+0.00000000001, 2)
54 if value < 0 {
55 return fmt.Sprintf("%s: \x0304%s%%\x03 ", field, v)
56 } else {
57 return fmt.Sprintf("%s: \x0303%s%%\x03 ", field, v)
58 }
59 } else {
60 v := humanize.CommafWithDigits(value+0.00000000001, 2)
61 return fmt.Sprintf("%s: $%s ", field, v)
62 }
63}
64
65func getStock(symbol, apiKey string) (string, error) {
66 req, err := http.NewRequest("GET", api_endpoint+"/stock/market/batch", nil)
67 if err != nil {
68 return "[Stocks] Request construction error.", err
69 }
70 req.Header.Add("User-Agent", "github.com/icyphox/paprika")
71 q := req.URL.Query()
72 q.Add("token", apiKey)
73 q.Add("symbols", symbol)
74 q.Add("types", "quote,stats")
75 req.URL.RawQuery = q.Encode()
76
77 res, err := stockClient.Do(req)
78 if err != nil {
79 return "[Stocks] API Client Error", err
80 }
81 defer res.Body.Close()
82
83 if res.StatusCode == 404 {
84 return fmt.Sprintf("[Stocks] Could not get quote for \x02%s\x02", symbol), nil
85 }
86
87 var resData map[string]tickerData
88 err = json.NewDecoder(res.Body).Decode(&resData)
89 if err != nil {
90 return "[Stock] API response malformed", err
91 }
92
93 quote := resData[symbol].Quote
94 stats := resData[symbol].Stats
95 var outRes strings.Builder
96 outRes.WriteString(fmt.Sprintf("\x02%s (%s)\x02 - ", stats.Company, quote.Symbol))
97 outRes.WriteString(formatMoneyNum("Current", quote.Current, false))
98 if quote.High != 0.0 {
99 outRes.WriteString(formatMoneyNum("High", quote.High, false))
100 outRes.WriteString(formatMoneyNum("Low", quote.Low, false))
101 }
102 outRes.WriteString(formatMoneyNum("24h", quote.ChangePercent, true))
103 outRes.WriteString(formatMoneyNum("5d", stats.Change5d, true))
104 outRes.WriteString(formatMoneyNum("6M", stats.Change6M, true))
105 outRes.WriteString(formatMoneyNum("1y", stats.Change1y, true))
106
107 return outRes.String(), nil
108}
109
110func (Stocks) Execute(m *irc.Message) (string, error) {
111 parsed := strings.SplitN(m.Trailing(), " ", 3)
112 if len(parsed) != 2 {
113 return fmt.Sprintf("Usage: %s <Ticker>", parsed[0]), nil
114 }
115 sym := strings.ToUpper(parsed[1])
116
117 if apiKey, ok := config.C.ApiKeys["iex"]; ok {
118 return getStock(sym, apiKey)
119 }
120
121 return "", NoIEXApi
122}