diff --git a/app/widget_maker.go b/app/widget_maker.go index 244b30cf..e51a1b6e 100644 --- a/app/widget_maker.go +++ b/app/widget_maker.go @@ -17,6 +17,7 @@ import ( "github.com/wtfutil/wtf/modules/devto" "github.com/wtfutil/wtf/modules/digitalclock" "github.com/wtfutil/wtf/modules/docker" + "github.com/wtfutil/wtf/modules/exchangerates" "github.com/wtfutil/wtf/modules/feedreader" "github.com/wtfutil/wtf/modules/football" "github.com/wtfutil/wtf/modules/gcal" @@ -260,6 +261,9 @@ func MakeWidget( case "zendesk": settings := zendesk.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = zendesk.NewWidget(app, pages, settings) + case "exchangerates": + settings := exchangerates.NewSettingsFromYAML(moduleName, moduleConfig, config) + widget = exchangerates.NewWidget(app, pages, settings) default: settings := unknown.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = unknown.NewWidget(app, settings) diff --git a/modules/exchangerates/exchangerates.go b/modules/exchangerates/exchangerates.go new file mode 100644 index 00000000..e3895a29 --- /dev/null +++ b/modules/exchangerates/exchangerates.go @@ -0,0 +1,42 @@ +package exchangerates + +import ( + "fmt" + "net/http" + + "github.com/wtfutil/wtf/utils" +) + +type Response struct { + Base string `json:"base"` + Rates map[string]float64 `json:"rates"` +} + +func FetchExchangeRates(settings *Settings) (map[string]map[string]float64, error) { + out := map[string]map[string]float64{} + + for base, rates := range settings.rates { + res, err := http.Get(fmt.Sprintf("https://api.exchangeratesapi.io/latest?base=%s", base)) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var resp Response + err = utils.ParseJSON(&resp, res.Body) + if err != nil { + return nil, err + } + + out[base] = map[string]float64{} + + for _, currency := range rates { + rate, ok := resp.Rates[currency] + if ok { + out[base][currency] = rate + } + } + } + + return out, nil +} diff --git a/modules/exchangerates/settings.go b/modules/exchangerates/settings.go new file mode 100644 index 00000000..ffdd6c6f --- /dev/null +++ b/modules/exchangerates/settings.go @@ -0,0 +1,46 @@ +// Package exchangerates +package exchangerates + +import ( + "github.com/olebedev/config" + "github.com/wtfutil/wtf/cfg" +) + +const ( + defaultFocusable = false + defaultTitle = "Exchange rates" +) + +// Settings defines the configuration properties for this module +type Settings struct { + common *cfg.Common + + rates map[string][]string `help:"Defines what currency rates we want to know about` +} + +// 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), + + rates: map[string][]string{}, + } + + raw := ymlConfig.UMap("rates", map[string]interface{}{}) + for key, value := range raw { + settings.rates[key] = []string{} + switch value.(type) { + case string: + settings.rates[key] = []string{value.(string)} + case []interface{}: + for _, currency := range value.([]interface{}) { + str, ok := currency.(string) + if ok { + settings.rates[key] = append(settings.rates[key], str) + } + } + } + } + + return &settings +} diff --git a/modules/exchangerates/widget.go b/modules/exchangerates/widget.go new file mode 100644 index 00000000..14f43167 --- /dev/null +++ b/modules/exchangerates/widget.go @@ -0,0 +1,69 @@ +// Package exchangerates +package exchangerates + +import ( + "fmt" + + "github.com/rivo/tview" + "github.com/wtfutil/wtf/view" +) + +type Widget struct { + view.ScrollableWidget + + settings *Settings + rates map[string]map[string]float64 + err error +} + +func NewWidget(app *tview.Application, pages *tview.Pages, settings *Settings) *Widget { + widget := Widget{ + ScrollableWidget: view.NewScrollableWidget(app, settings.common), + + 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 + } + + // The last call should always be to the display function + widget.Render() +} + +/* -------------------- Unexported Functions -------------------- */ + +func (widget *Widget) Render() { + widget.Redraw(widget.content) +} + +func (widget *Widget) content() (string, string, bool) { + var out string + + if widget.err != nil { + out = widget.err.Error() + } else { + for base, rates := range widget.rates { + out += fmt.Sprintf("[%s]Rates from %s[white]\n", widget.settings.common.Colors.Subheading, base) + idx := 0 + for cur, rate := range rates { + out += fmt.Sprintf("[%s]%s - %f[white]\n", widget.CommonSettings().RowColor(idx), cur, rate) + idx++ + } + } + } + + return widget.CommonSettings().Title, out, false +}