diff --git a/modules/finnhub/client.go b/modules/finnhub/client.go new file mode 100644 index 00000000..320a048d --- /dev/null +++ b/modules/finnhub/client.go @@ -0,0 +1,77 @@ +package finnhub + +import ( + "fmt" + "net/http" + "net/url" + "encoding/json" + //"github.com/wtfutil/wtf/utils" +) + +// Client .. +type Client struct { + symbols []string + apiKey string +} + +// NewClient .. +func NewClient(symbols []string, apiKey string) *Client { + client := Client{ + symbols: symbols, + apiKey: apiKey, + } + + return &client +} + +// Getquote .. +func (client *Client) Getquote() ([]Quote, error) { + quotes := []Quote{} + + for _, s := range client.symbols { + resp, err := client.finnhubRequest(s) + if err != nil { + return quotes, err + } + + var quote Quote + quote.Stock = s + json.NewDecoder(resp.Body).Decode("e) + quotes = append(quotes, quote) + } + + return quotes, nil +} + +/* -------------------- Unexported Functions -------------------- */ + +var ( + finnhubURL = &url.URL{Scheme: "https", Host: "finnhub.io", Path: "/api/v1/quote"} +) + +func (client *Client) finnhubRequest(symbol string) (*http.Response, error) { + params := url.Values{} + params.Add("symbol", symbol) + params.Add("token", client.apiKey) + + url := finnhubURL.ResolveReference(&url.URL{RawQuery: params.Encode()}) + + req, err := http.NewRequest("GET", url.String(), nil) + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + if err != nil { + return nil, err + } + + httpClient := &http.Client{} + resp, err := httpClient.Do(req) + if err != nil { + return nil, err + } + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return nil, fmt.Errorf(resp.Status) + } + + return resp, nil +} diff --git a/modules/finnhub/client_test.go b/modules/finnhub/client_test.go new file mode 100644 index 00000000..91647ee7 --- /dev/null +++ b/modules/finnhub/client_test.go @@ -0,0 +1,24 @@ +package finnhub + +import ( + "fmt" + "testing" +) +func TestFinnhubClient(t *testing.T){ + testClient := &Client { + symbols: []string{ + "AAPL", + "MSFT", + }, + apiKey: "", + } + + r, err := testClient.Getquote() + if err != nil { + fmt.Println(err) + } + + fmt.Println(r[0].Stock, r[0].C) + fmt.Println(r[1].Stock, r[1].C) + +} \ No newline at end of file diff --git a/modules/finnhub/finnhub.go b/modules/finnhub/finnhub.go deleted file mode 100644 index c4f5d98a..00000000 --- a/modules/finnhub/finnhub.go +++ /dev/null @@ -1,37 +0,0 @@ -package finnhub - -import ( - "fmt" - "encoding/json" - "github.com/gorilla/websocket" -) - - -func FetchExchangeRates(settings *Settings) (map[string]map[string]float64, error) { - out := map[string]map[string]float64{} - - for base, rates := range settings.rates { - resp, err := http.Get(fmt.Sprintf("https://api.exchangeratesapi.io/latest?base=%s", base)) - if err != nil { - return nil, err - } - defer func() { _ = resp.Body.Close() }() - - var data Response - err = utils.ParseJSON(&data, resp.Body) - if err != nil { - return nil, err - } - - out[base] = map[string]float64{} - - for _, currency := range rates { - rate, ok := data.Rates[currency] - if ok { - out[base][currency] = rate - } - } - } - - return out, nil -} diff --git a/modules/finnhub/quote.go b/modules/finnhub/quote.go new file mode 100644 index 00000000..b8e9af86 --- /dev/null +++ b/modules/finnhub/quote.go @@ -0,0 +1,12 @@ +package finnhub + +type Quote struct { + C float64 `json:"c"` + H float64 `json:"h"` + L float64 `json:"l"` + O float64 `json:"o"` + Pc float64 `json:"pc"` + T int `json:"t"` + + Stock string +} \ No newline at end of file diff --git a/modules/finnhub/settings.go b/modules/finnhub/settings.go index 330ac912..20832e1a 100644 --- a/modules/finnhub/settings.go +++ b/modules/finnhub/settings.go @@ -3,50 +3,34 @@ package finnhub import ( "github.com/olebedev/config" "github.com/wtfutil/wtf/cfg" + "github.com/wtfutil/wtf/utils" + "os" ) const ( - defaultFocusable = false - defaultTitle = "Exchange rates" + defaultFocusable = true + defaultTitle = "Stocks" ) // Settings defines the configuration properties for this module type Settings struct { common *cfg.Common - precision int `help:"How many decimal places to display." optional:"true"` - - rates map[string][]string `help:"Defines what currency rates we want to know about"` - order []string + apiKey string `help:"Your finnhub API token."` + symbols []string `help:"An array of stocks symbols (i.e. AAPL, MSFT)"` } // NewSettingsFromYAML creates a new settings instance from a YAML config block func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { + settings := Settings{ common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), - precision: ymlConfig.UInt("precision", 7), - - rates: map[string][]string{}, - order: []string{}, + apiKey: ymlConfig.UString("apiKey", ymlConfig.UString("apikey", os.Getenv("WTF_FINNHUB_API_KEY"))), + symbols: utils.ToStrs(ymlConfig.UList("symbols")), } - raw := ymlConfig.UMap("rates", map[string]interface{}{}) - for key, value := range raw { - settings.order = append(settings.order, key) - settings.rates[key] = []string{} - switch value := value.(type) { - case string: - settings.rates[key] = []string{value} - case []interface{}: - for _, currency := range value { - str, ok := currency.(string) - if ok { - settings.rates[key] = append(settings.rates[key], str) - } - } - } - } + cfg.ModuleSecret(name, globalConfig, &settings.apiKey).Load() return &settings } diff --git a/modules/finnhub/widget.go b/modules/finnhub/widget.go index 1ce623ca..fd6fdad1 100644 --- a/modules/finnhub/widget.go +++ b/modules/finnhub/widget.go @@ -2,99 +2,65 @@ package finnhub import ( "fmt" - "regexp" - "sort" "github.com/rivo/tview" "github.com/wtfutil/wtf/view" - "github.com/wtfutil/wtf/wtf" ) +// Widget .. type Widget struct { - view.ScrollableWidget + view.TextWidget + *Client settings *Settings - rates map[string]map[string]float64 - err error } -func NewWidget(app *tview.Application, pages *tview.Pages, settings *Settings) *Widget { +// NewWidget .. +func NewWidget(app *tview.Application, settings *Settings) *Widget { widget := Widget{ - ScrollableWidget: view.NewScrollableWidget(app, settings.common), + TextWidget: view.NewTextWidget(app, settings.common), + Client: NewClient(settings.symbols, settings.apiKey), settings: settings, } - widget.SetRenderFunction(widget.Render) - return &widget } /* -------------------- Exported Functions -------------------- */ func (widget *Widget) Refresh() { - - rates, err := FetchExchangeRates(widget.settings) - if err != nil { - widget.err = err - } else { - widget.rates = rates + if widget.Disabled() { + return } - // The last call should always be to the display function - widget.Render() -} - -func (widget *Widget) Render() { widget.Redraw(widget.content) } /* -------------------- Unexported Functions -------------------- */ func (widget *Widget) content() (string, string, bool) { - if widget.err != nil { - widget.View.SetWrap(true) - return widget.CommonSettings().Title, widget.err.Error(), false - } + quotes, err := widget.Client.Getquote() - // Sort the bases alphabetically to ensure consistent display ordering - bases := []string{} - for base := range widget.settings.rates { - bases = append(bases, base) - } - sort.Strings(bases) + title := fmt.Sprintf("%s - from finnhub api", widget.CommonSettings().Title) + var str string + wrap := false + if err != nil { + wrap = true + str = err.Error() + } else { + for idx, q := range quotes { + if idx > 10 { + break + } - out := "" - - for idx, base := range bases { - rates := widget.settings.rates[base] - - rowColor := widget.CommonSettings().RowColor(idx) - - for _, cur := range rates { - rate := widget.rates[base][cur] - - out += fmt.Sprintf( - "[%s]1 %s = %s %s[white]\n", - rowColor, - base, - widget.formatConversionRate(rate), - cur, + str += fmt.Sprintf( + "[%s]: %s \n", + q.Stock, + q.C, ) - - idx++ } } - widget.View.SetWrap(false) - return widget.CommonSettings().Title, out, false -} - -// formatConversionRate takes the raw conversion float and formats it to the precision the -// user specifies in their config (or to the default value) -func (widget *Widget) formatConversionRate(rate float64) string { - rate = wtf.TruncateFloat64(rate, widget.settings.precision) - - r, _ := regexp.Compile(`\.?0*$`) - return r.ReplaceAllString(fmt.Sprintf("%10.7f", rate), "") + return title, str, wrap }