Fix coin name to ID lookup. #81

pull/94/head
Miguel Mota 3 years ago
parent 7fa0af0fa3
commit a9473e354c

@ -130,7 +130,7 @@ func (ct *Cointop) ChartPoints(symbol string, name string) error {
if found {
// cache hit
data, _ = cached.([]float64)
ct.debuglog("soft cache hit")
ct.debuglog("ct.ChartPoints() soft cache hit")
}
if len(data) == 0 {

@ -43,15 +43,24 @@ const dots = "..."
func (ct *Cointop) RefreshTable() error {
ct.debuglog("refreshTable()")
statusText := ""
switch ct.State.selectedView {
case PortfolioView:
ct.table = ct.GetPortfolioTable()
if ct.table.RowCount() == 0 {
statusText = "No holdings found. Press \"e\" on a coin to edit holdings."
}
case PriceAlertsView:
ct.table = ct.GetPriceAlertsTable()
if ct.table.RowCount() == 0 {
statusText = "No price alerts found. Press \"+\" on a coin to add a price alert."
}
default:
ct.table = ct.GetCoinsTable()
if ct.table.RowCount() == 0 {
statusText = "no coin data"
}
}
ct.table.HideColumHeaders = true
// highlight last row if current row is out of bounds (can happen when switching views)
@ -62,7 +71,11 @@ func (ct *Cointop) RefreshTable() error {
ct.UpdateUI(func() error {
ct.Views.Table.Clear()
ct.table.Format().Fprint(ct.Views.Table.Backing())
if statusText == "" {
ct.table.Format().Fprint(ct.Views.Table.Backing())
} else {
ct.Views.Table.Update(fmt.Sprintf("\n\n%s", statusText))
}
go ct.RowChanged()
go ct.UpdateTableHeader()
go ct.UpdateMarketbar()

@ -5,6 +5,7 @@ import (
"fmt"
"strconv"
"strings"
"sync"
"time"
apitypes "github.com/miguelmota/cointop/pkg/api/types"
@ -24,16 +25,20 @@ type Service struct {
client *gecko.Client
maxResultsPerPage int
maxPages int
cacheMap sync.Map
}
// NewCoinGecko new service
func NewCoinGecko() *Service {
client := gecko.NewClient(nil)
return &Service{
svc := &Service{
client: client,
maxResultsPerPage: 250,
maxPages: 10,
cacheMap: sync.Map{},
}
svc.cacheCoinsIDList()
return svc
}
// Ping ping API
@ -45,88 +50,6 @@ func (s *Service) Ping() error {
return nil
}
func (s *Service) getPaginatedCoinData(convert string, offset int, names []string) ([]apitypes.Coin, error) {
var ret []apitypes.Coin
page := offset + 1 // page starts at 1
sparkline := false
pcp := geckoTypes.PriceChangePercentageObject
priceChangePercentage := []string{
pcp.PCP1h,
pcp.PCP24h,
pcp.PCP7d,
pcp.PCP30d,
}
order := geckoTypes.OrderTypeObject.MarketCapDesc
convertTo := strings.ToLower(convert)
if convertTo == "" {
convertTo = "usd"
}
ids := make([]string, len(names))
for i, name := range names {
slug := util.NameToSlug(name)
ids[i] = slug
}
list, err := s.client.CoinsMarket(convertTo, ids, order, s.maxResultsPerPage, page, sparkline, priceChangePercentage)
if err != nil {
return nil, err
}
if list != nil {
// for fetching "simple prices"
currencies := make([]string, len(*list))
for i, item := range *list {
currencies[i] = item.Name
}
for _, item := range *list {
price := item.CurrentPrice
var percentChange1H float64
var percentChange24H float64
var percentChange7D float64
var percentChange30D float64
if item.PriceChangePercentage1hInCurrency != nil {
percentChange1H = *item.PriceChangePercentage1hInCurrency
}
if item.PriceChangePercentage24hInCurrency != nil {
percentChange24H = *item.PriceChangePercentage24hInCurrency
}
if item.PriceChangePercentage7dInCurrency != nil {
percentChange7D = *item.PriceChangePercentage7dInCurrency
}
if item.PriceChangePercentage30dInCurrency != nil {
percentChange30D = *item.PriceChangePercentage30dInCurrency
}
availableSupply := item.CirculatingSupply
totalSupply := item.TotalSupply
if totalSupply == 0 {
totalSupply = availableSupply
}
ret = append(ret, apitypes.Coin{
ID: util.FormatID(item.ID),
Name: util.FormatName(item.Name),
Symbol: util.FormatSymbol(item.Symbol),
Rank: util.FormatRank(item.MarketCapRank),
AvailableSupply: util.FormatSupply(availableSupply),
TotalSupply: util.FormatSupply(totalSupply),
MarketCap: util.FormatMarketCap(item.MarketCap),
Price: util.FormatPrice(price, convert),
PercentChange1H: util.FormatPercentChange(percentChange1H),
PercentChange24H: util.FormatPercentChange(percentChange24H),
PercentChange7D: util.FormatPercentChange(percentChange7D),
PercentChange30D: util.FormatPercentChange(percentChange30D),
Volume24H: util.FormatVolume(item.TotalVolume),
LastUpdated: util.FormatLastUpdated(item.LastUpdated),
})
}
}
return ret, nil
}
// GetAllCoinData gets all coin data. Need to paginate through all pages
func (s *Service) GetAllCoinData(convert string, ch chan []apitypes.Coin) error {
go func() {
@ -173,7 +96,7 @@ func (s *Service) GetCoinDataBatch(names []string, convert string) ([]apitypes.C
func (s *Service) GetCoinGraphData(convert, symbol, name string, start, end int64) (apitypes.CoinGraph, error) {
ret := apitypes.CoinGraph{}
days := strconv.Itoa(util.CalcDays(start, end))
chart, err := s.client.CoinsIDMarketChart(util.NameToSlug(name), convert, days)
chart, err := s.client.CoinsIDMarketChart(s.coinNameToID(name), convert, days)
if err != nil {
return ret, err
}
@ -259,18 +182,7 @@ func (s *Service) GetGlobalMarketData(convert string) (apitypes.GlobalMarketData
// Price returns the current price of the coin
func (s *Service) Price(name string, convert string) (float64, error) {
list, err := s.client.CoinsList()
if err != nil {
return 0, err
}
for _, item := range *list {
if item.Symbol == strings.ToLower(name) {
name = item.Name
}
}
ids := []string{util.NameToSlug(name)}
ids := []string{s.coinNameToID(name)}
convert = strings.ToLower(convert)
currencies := []string{convert}
priceList, err := s.client.SimplePrice(ids, currencies)
@ -289,8 +201,8 @@ func (s *Service) Price(name string, convert string) (float64, error) {
// CoinLink returns the URL link for the coin
func (s *Service) CoinLink(name string) string {
slug := util.NameToSlug(name)
return fmt.Sprintf("https://www.coingecko.com/en/coins/%s", slug)
ID := s.coinNameToID(name)
return fmt.Sprintf("https://www.coingecko.com/en/coins/%s", ID)
}
// SupportedCurrencies returns a list of supported currencies
@ -350,3 +262,134 @@ func (s *Service) SupportedCurrencies() []string {
"ZAR",
}
}
// cacheCoinsIDList fetches list of all coin IDS by name and symbols and caches it in a map for fast lookups
func (s *Service) cacheCoinsIDList() error {
list, err := s.client.CoinsList()
if err != nil {
return err
}
if list == nil {
return nil
}
firstWords := [][]string{}
for _, item := range *list {
keys := []string{
strings.ToLower(item.Name),
strings.ToLower(item.Symbol),
util.NameToSlug(item.Name),
}
parts := strings.Split(strings.ToLower(item.Name), " ")
if len(parts) > 1 {
if parts[1] == "coin" {
keys = append(keys, parts[0])
} else {
firstWords = append(firstWords, []string{parts[0], item.ID})
}
}
for _, key := range keys {
_, exists := s.cacheMap.Load(key)
if !exists {
s.cacheMap.Store(key, item.ID)
}
}
}
for _, parts := range firstWords {
_, exists := s.cacheMap.Load(parts[0])
if !exists {
s.cacheMap.Store(parts[0], parts[1])
}
}
return nil
}
// coinNameToID attempts to get coin ID based on coin name or coin symbol
func (s *Service) coinNameToID(name string) string {
id, ok := s.cacheMap.Load(strings.ToLower(strings.TrimSpace(name)))
if ok {
return id.(string)
}
return util.NameToSlug(name)
}
// getPaginatedCoinData fetches coin data from page offset
func (s *Service) getPaginatedCoinData(convert string, offset int, names []string) ([]apitypes.Coin, error) {
var ret []apitypes.Coin
page := offset + 1 // page starts at 1
sparkline := false
pcp := geckoTypes.PriceChangePercentageObject
priceChangePercentage := []string{
pcp.PCP1h,
pcp.PCP24h,
pcp.PCP7d,
pcp.PCP30d,
}
order := geckoTypes.OrderTypeObject.MarketCapDesc
convertTo := strings.ToLower(convert)
if convertTo == "" {
convertTo = "usd"
}
ids := make([]string, len(names))
for i, name := range names {
ids[i] = s.coinNameToID(name)
}
list, err := s.client.CoinsMarket(convertTo, ids, order, s.maxResultsPerPage, page, sparkline, priceChangePercentage)
if err != nil {
return nil, err
}
if list != nil {
// for fetching "simple prices"
currencies := make([]string, len(*list))
for i, item := range *list {
currencies[i] = item.Name
}
for _, item := range *list {
price := item.CurrentPrice
var percentChange1H float64
var percentChange24H float64
var percentChange7D float64
var percentChange30D float64
if item.PriceChangePercentage1hInCurrency != nil {
percentChange1H = *item.PriceChangePercentage1hInCurrency
}
if item.PriceChangePercentage24hInCurrency != nil {
percentChange24H = *item.PriceChangePercentage24hInCurrency
}
if item.PriceChangePercentage7dInCurrency != nil {
percentChange7D = *item.PriceChangePercentage7dInCurrency
}
if item.PriceChangePercentage30dInCurrency != nil {
percentChange30D = *item.PriceChangePercentage30dInCurrency
}
availableSupply := item.CirculatingSupply
totalSupply := item.TotalSupply
if totalSupply == 0 {
totalSupply = availableSupply
}
ret = append(ret, apitypes.Coin{
ID: util.FormatID(item.ID),
Name: util.FormatName(item.Name),
Symbol: util.FormatSymbol(item.Symbol),
Rank: util.FormatRank(item.MarketCapRank),
AvailableSupply: util.FormatSupply(availableSupply),
TotalSupply: util.FormatSupply(totalSupply),
MarketCap: util.FormatMarketCap(item.MarketCap),
Price: util.FormatPrice(price, convert),
PercentChange1H: util.FormatPercentChange(percentChange1H),
PercentChange24H: util.FormatPercentChange(percentChange24H),
PercentChange7D: util.FormatPercentChange(percentChange7D),
PercentChange30D: util.FormatPercentChange(percentChange30D),
Volume24H: util.FormatVolume(item.TotalVolume),
LastUpdated: util.FormatLastUpdated(item.LastUpdated),
})
}
}
return ret, nil
}

@ -246,6 +246,11 @@ func (t *Table) Fprint(w io.Writer) {
}
}
// RowCount returns the number of rows
func (t *Table) RowCount() int {
return len(t.rows)
}
// RowCell is a row cell struct
type RowCell struct {
LeftMargin int

Loading…
Cancel
Save