mirror of
https://github.com/taigrr/wtf
synced 2025-01-18 04:03:14 -08:00
added finnhub mod
This commit is contained in:
parent
63462ac8ef
commit
4d3e9506e4
77
modules/finnhub/client.go
Normal file
77
modules/finnhub/client.go
Normal file
@ -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
|
||||||
|
}
|
24
modules/finnhub/client_test.go
Normal file
24
modules/finnhub/client_test.go
Normal file
@ -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)
|
||||||
|
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
12
modules/finnhub/quote.go
Normal file
12
modules/finnhub/quote.go
Normal file
@ -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
|
||||||
|
}
|
@ -3,50 +3,34 @@ package finnhub
|
|||||||
import (
|
import (
|
||||||
"github.com/olebedev/config"
|
"github.com/olebedev/config"
|
||||||
"github.com/wtfutil/wtf/cfg"
|
"github.com/wtfutil/wtf/cfg"
|
||||||
|
"github.com/wtfutil/wtf/utils"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultFocusable = false
|
defaultFocusable = true
|
||||||
defaultTitle = "Exchange rates"
|
defaultTitle = "Stocks"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Settings defines the configuration properties for this module
|
// Settings defines the configuration properties for this module
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
common *cfg.Common
|
common *cfg.Common
|
||||||
|
|
||||||
precision int `help:"How many decimal places to display." optional:"true"`
|
apiKey string `help:"Your finnhub API token."`
|
||||||
|
symbols []string `help:"An array of stocks symbols (i.e. AAPL, MSFT)"`
|
||||||
rates map[string][]string `help:"Defines what currency rates we want to know about"`
|
|
||||||
order []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSettingsFromYAML creates a new settings instance from a YAML config block
|
// NewSettingsFromYAML creates a new settings instance from a YAML config block
|
||||||
func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
|
func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
|
||||||
|
|
||||||
settings := Settings{
|
settings := Settings{
|
||||||
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
|
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
|
||||||
|
|
||||||
precision: ymlConfig.UInt("precision", 7),
|
apiKey: ymlConfig.UString("apiKey", ymlConfig.UString("apikey", os.Getenv("WTF_FINNHUB_API_KEY"))),
|
||||||
|
symbols: utils.ToStrs(ymlConfig.UList("symbols")),
|
||||||
rates: map[string][]string{},
|
|
||||||
order: []string{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
raw := ymlConfig.UMap("rates", map[string]interface{}{})
|
cfg.ModuleSecret(name, globalConfig, &settings.apiKey).Load()
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &settings
|
return &settings
|
||||||
}
|
}
|
||||||
|
@ -2,99 +2,65 @@ package finnhub
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
"github.com/wtfutil/wtf/view"
|
"github.com/wtfutil/wtf/view"
|
||||||
"github.com/wtfutil/wtf/wtf"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Widget ..
|
||||||
type Widget struct {
|
type Widget struct {
|
||||||
view.ScrollableWidget
|
view.TextWidget
|
||||||
|
*Client
|
||||||
|
|
||||||
settings *Settings
|
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{
|
widget := Widget{
|
||||||
ScrollableWidget: view.NewScrollableWidget(app, settings.common),
|
TextWidget: view.NewTextWidget(app, settings.common),
|
||||||
|
Client: NewClient(settings.symbols, settings.apiKey),
|
||||||
|
|
||||||
settings: settings,
|
settings: settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
widget.SetRenderFunction(widget.Render)
|
|
||||||
|
|
||||||
return &widget
|
return &widget
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------- Exported Functions -------------------- */
|
/* -------------------- Exported Functions -------------------- */
|
||||||
|
|
||||||
func (widget *Widget) Refresh() {
|
func (widget *Widget) Refresh() {
|
||||||
|
if widget.Disabled() {
|
||||||
rates, err := FetchExchangeRates(widget.settings)
|
return
|
||||||
if err != nil {
|
|
||||||
widget.err = err
|
|
||||||
} else {
|
|
||||||
widget.rates = rates
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The last call should always be to the display function
|
|
||||||
widget.Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (widget *Widget) Render() {
|
|
||||||
widget.Redraw(widget.content)
|
widget.Redraw(widget.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------- Unexported Functions -------------------- */
|
/* -------------------- Unexported Functions -------------------- */
|
||||||
|
|
||||||
func (widget *Widget) content() (string, string, bool) {
|
func (widget *Widget) content() (string, string, bool) {
|
||||||
if widget.err != nil {
|
quotes, err := widget.Client.Getquote()
|
||||||
widget.View.SetWrap(true)
|
|
||||||
return widget.CommonSettings().Title, widget.err.Error(), false
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the bases alphabetically to ensure consistent display ordering
|
str += fmt.Sprintf(
|
||||||
bases := []string{}
|
"[%s]: %s \n",
|
||||||
for base := range widget.settings.rates {
|
q.Stock,
|
||||||
bases = append(bases, base)
|
q.C,
|
||||||
}
|
|
||||||
sort.Strings(bases)
|
|
||||||
|
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
idx++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
widget.View.SetWrap(false)
|
return title, str, wrap
|
||||||
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), "")
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user