2
0
mirror of https://github.com/miguelmota/cointop synced 2024-11-10 13:10:26 +00:00
cointop/vendor/github.com/miguelmota/go-coinmarketcap/v2/v2.go
2019-07-01 22:47:54 -07:00

405 lines
9.9 KiB
Go

// Package coinmarketcap Coin Market Cap API client for Go
package coinmarketcap
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
"sort"
"github.com/anaskhan96/soup"
"github.com/miguelmota/go-coinmarketcap/v2/types"
)
var (
siteURL = "https://coinmarketcap.com"
baseURL = "https://api.coinmarketcap.com/v2"
coinGraphURL = "https://graphs2.coinmarketcap.com/currencies"
globalMarketGraphURL = "https://graphs2.coinmarketcap.com/global/marketcap-total"
altcoinMarketGraphURL = "https://graphs2.coinmarketcap.com/global/marketcap-altcoin"
)
// Interface interface
type Interface interface {
Listings() ([]*types.Listing, error)
Tickers(options *TickersOptions) ([]*types.Ticker, error)
Ticker(options *TickerOptions) (*types.Ticker, error)
TickerGraph(options *TickerGraphOptions) (*types.TickerGraph, error)
GlobalMarket(options *GlobalMarketOptions) (*types.GlobalMarket, error)
GlobalMarketGraph(options *GlobalMarketGraphOptions) (*types.MarketGraph, error)
GlobalAltcoinMarketGraph(options *GlobalAltcoinMarketGraphOptions) (*types.MarketGraph, error)
Markets(options *MarketsOptions) ([]*types.Market, error)
Price(options *PriceOptions) (float64, error)
CoinID(symbol string) (int, error)
CoinSlug(symbol string) (string, error)
CoinSymbol(slug string) (string, error)
}
// listingsMedia listings response media
type listingsMedia struct {
Data []*types.Listing `json:"data"`
}
// Listings gets all coin listings
func Listings() ([]*types.Listing, error) {
url := fmt.Sprintf("%s/listings", baseURL)
resp, err := makeReq(url)
var body listingsMedia
err = json.Unmarshal(resp, &body)
if err != nil {
return nil, err
}
return body.Data, nil
}
// TickersOptions options for tickers method
type TickersOptions struct {
Start int
Limit int
Convert string
Sort string
}
// tickerMedia tickers response media
type tickersMedia struct {
Data map[string]*types.Ticker `json:"data,omitempty"`
Metadata struct {
Timestamp int64
NumCryptoCurrencies int `json:"num_cryptocurrencies,omitempty"`
Error string `json:",omitempty"`
}
}
// Tickers gets ticker information on coins
func Tickers(options *TickersOptions) ([]*types.Ticker, error) {
var params []string
if options.Start >= 0 {
params = append(params, fmt.Sprintf("start=%v", options.Start))
}
if options.Limit >= 0 {
params = append(params, fmt.Sprintf("limit=%v", options.Limit))
}
if options.Convert != "" {
params = append(params, fmt.Sprintf("convert=%v", options.Convert))
}
if options.Sort != "" {
params = append(params, fmt.Sprintf("sort=%v", options.Sort))
}
url := fmt.Sprintf("%s/ticker?%s", baseURL, strings.Join(params, "&"))
resp, err := makeReq(url)
var body tickersMedia
err = json.Unmarshal(resp, &body)
if err != nil {
return nil, err
}
data := body.Data
var tickers []*types.Ticker
for _, v := range data {
tickers = append(tickers, v)
}
if body.Metadata.Error != "" {
return nil, errors.New(body.Metadata.Error)
}
sort.Slice(tickers, func(i, j int) bool {
return tickers[i].Rank < tickers[j].Rank
})
return tickers, nil
}
// TickerOptions options for ticker method
type TickerOptions struct {
Symbol string
Convert string
}
type tickerMedia struct {
Data *types.Ticker `json:"data"`
}
// Ticker gets ticker information about a cryptocurrency
func Ticker(options *TickerOptions) (*types.Ticker, error) {
var params []string
if options.Convert != "" {
params = append(params, fmt.Sprintf("convert=%v", options.Convert))
}
id, err := CoinID(options.Symbol)
if err != nil {
return nil, err
}
url := fmt.Sprintf("%s/ticker/%v?%s", baseURL, id, strings.Join(params, "&"))
resp, err := makeReq(url)
if err != nil {
return nil, err
}
var body tickerMedia
err = json.Unmarshal(resp, &body)
if err != nil {
return nil, err
}
return body.Data, nil
}
// TickerGraphOptions options for ticker graph
type TickerGraphOptions struct {
Symbol string
Start int64
End int64
}
// TickerGraph gets graph data points for a cryptocurrency
func TickerGraph(options *TickerGraphOptions) (*types.TickerGraph, error) {
slug, err := CoinSlug(options.Symbol)
if err != nil {
return nil, err
}
url := fmt.Sprintf("%s/%s/%d/%d", coinGraphURL, slug, options.Start*1000, options.End*1000)
resp, err := makeReq(url)
if err != nil {
return nil, err
}
var data *types.TickerGraph
err = json.Unmarshal(resp, &data)
if err != nil {
return nil, err
}
return data, nil
}
// GlobalMarketOptions options for global data method
type GlobalMarketOptions struct {
Convert string
}
// globalMedia global data response media
type globalMarketMedia struct {
Data *types.GlobalMarket `json:"data"`
}
// GlobalMarket gets information about the global market of the cryptocurrencies
func GlobalMarket(options *GlobalMarketOptions) (*types.GlobalMarket, error) {
var params []string
if options.Convert != "" {
params = append(params, fmt.Sprintf("convert=%v", options.Convert))
}
url := fmt.Sprintf("%s/global?%s", baseURL, strings.Join(params, "&"))
resp, err := makeReq(url)
var body globalMarketMedia
err = json.Unmarshal(resp, &body)
if err != nil {
return nil, err
}
return body.Data, nil
}
// GlobalMarketGraphOptions options for global market graph method
type GlobalMarketGraphOptions struct {
Start int64
End int64
}
// GlobalMarketGraph get graph data points of global market
func GlobalMarketGraph(options *GlobalMarketGraphOptions) (*types.MarketGraph, error) {
url := fmt.Sprintf("%s/%d/%d", globalMarketGraphURL, options.Start*1000, options.End*1000)
resp, err := makeReq(url)
if err != nil {
return nil, err
}
var data *types.MarketGraph
err = json.Unmarshal(resp, &data)
if err != nil {
return nil, err
}
return data, nil
}
// GlobalAltcoinMarketGraphOptions options for global altcoin market graph method
type GlobalAltcoinMarketGraphOptions struct {
Start int64
End int64
}
// GlobalAltcoinMarketGraph gets graph data points of altcoin market
func GlobalAltcoinMarketGraph(options *GlobalAltcoinMarketGraphOptions) (*types.MarketGraph, error) {
url := fmt.Sprintf("%s/%d/%d", altcoinMarketGraphURL, options.Start*1000, options.End*1000)
resp, err := makeReq(url)
if err != nil {
return nil, err
}
var data *types.MarketGraph
err = json.Unmarshal(resp, &data)
if err != nil {
return nil, err
}
return data, nil
}
// MarketsOptions options for markets method
type MarketsOptions struct {
Symbol string
}
// Markets get market data for a cryptocurrency
func Markets(options *MarketsOptions) ([]*types.Market, error) {
slug, err := CoinSlug(options.Symbol)
if err != nil {
return nil, err
}
url := fmt.Sprintf("%s/currencies/%s/#markets", siteURL, slug)
var markets []*types.Market
response, err := soup.Get(url)
if err != nil {
return nil, err
}
rows := soup.HTMLParse(response).Find("table", "id", "markets-table").Find("tbody").FindAll("tr")
for _, row := range rows {
var data []string
for _, column := range row.FindAll("td") {
attrs := column.Attrs()
if attrs["data-sort"] != "" {
data = append(data, attrs["data-sort"])
} else {
data = append(data, column.Text())
}
}
markets = append(markets, &types.Market{
Rank: toInt(data[0]),
Exchange: data[1],
Pair: data[2],
VolumeUSD: toFloat(data[3]),
Price: toFloat(data[4]),
VolumePercent: toFloat(data[5]),
Updated: data[6],
})
}
return markets, nil
}
// PriceOptions options for price method
type PriceOptions struct {
Symbol string
Convert string
}
// Price gets price of a cryptocurrency
func Price(options *PriceOptions) (float64, error) {
coin, err := Ticker(&TickerOptions{
Convert: options.Convert,
Symbol: options.Symbol,
})
if err != nil {
return 0, err
}
if coin == nil {
return 0, errors.New("coin not found")
}
return coin.Quotes[options.Convert].Price, nil
}
// CoinID gets the ID for the cryptocurrency
func CoinID(symbol string) (int, error) {
symbol = strings.ToUpper(strings.TrimSpace(symbol))
listings, err := Listings()
if err != nil {
return 0, err
}
for _, l := range listings {
if l.Symbol == symbol {
return l.ID, nil
}
if l.Slug == strings.ToLower(symbol) {
return l.ID, nil
}
}
return 0, errors.New("coin not found")
}
// CoinSlug gets the slug for the cryptocurrency
func CoinSlug(symbol string) (string, error) {
symbol = strings.ToUpper(strings.TrimSpace(symbol))
coin, err := Ticker(&TickerOptions{
Symbol: symbol,
})
if err != nil {
return "", err
}
if coin == nil {
return "", errors.New("coin not found")
}
return coin.Slug, nil
}
// CoinSymbol gets the symbol for the cryptocurrency
func CoinSymbol(slug string) (string, error) {
slug = strings.ToLower(strings.TrimSpace(slug))
coin, err := Ticker(&TickerOptions{
Symbol: slug,
})
if err != nil {
return "", err
}
if coin == nil {
return "", errors.New("coin not found")
}
return coin.Symbol, nil
}
// toInt helper for parsing strings to int
func toInt(rawInt string) int {
parsed, _ := strconv.Atoi(strings.Replace(strings.Replace(rawInt, "$", "", -1), ",", "", -1))
return parsed
}
// toFloat helper for parsing strings to float
func toFloat(rawFloat string) float64 {
parsed, _ := strconv.ParseFloat(strings.Replace(strings.Replace(strings.Replace(rawFloat, "$", "", -1), ",", "", -1), "%", "", -1), 64)
return parsed
}
// doReq HTTP client
func doReq(req *http.Request) ([]byte, error) {
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if 200 != resp.StatusCode {
return nil, fmt.Errorf("%s", body)
}
return body, nil
}
// makeReq HTTP request helper
func makeReq(url string) ([]byte, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
resp, err := doReq(req)
if err != nil {
return nil, err
}
return resp, err
}