Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
judemont committed Dec 5, 2024
0 parents commit d1c21ab
Show file tree
Hide file tree
Showing 7 changed files with 361 additions and 0 deletions.
79 changes: 79 additions & 0 deletions api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package main

import (
"encoding/json"
"errors"
"io"
"math/rand"
"net/http"
)

const BASE_API_URL string = "https://openapiv1.coinstats.app"

var API_KEYS = [1]string{"vohN0pzG3mjbLsVYXFjyL+soUlp5tnqvuJQo7X74sxY="}

func getCoinData(cointId string) (Crypto, error) {

url := BASE_API_URL + "/coins/" + cointId

response := apiCall(url)

var responseMap map[string]interface{}

json.Unmarshal(response, &responseMap)

if statusCode, ok := responseMap["statusCode"]; ok && statusCode != http.StatusOK {
return Crypto{}, errors.New(responseMap["message"].(string))
}

var crypto Crypto = Crypto{
ID: responseMap["id"].(string),
Name: responseMap["name"].(string),
Symbol: responseMap["symbol"].(string),
Price: responseMap["price"].(float64),
LogoURL: responseMap["icon"].(string),
PriceChangePercentageDay: responseMap["priceChange1d"].(float64),
Website: responseMap["websiteUrl"].(string),
MarketCap: responseMap["marketCap"].(float64),
MarketCapRank: int(responseMap["rank"].(float64)),
TotalSupply: responseMap["totalSupply"].(float64),
CirculatingSupply: responseMap["availableSupply"].(float64),
Volume: responseMap["volume"].(float64),
}
return crypto, nil

}

func getChart(coinId string, period string) ([][]float64, error) {
url := BASE_API_URL + "/coins/" + coinId + "/charts?period=" + period

response := apiCall(url)

var responseMap map[string]interface{}
json.Unmarshal(response, &responseMap)

if statusCode, ok := responseMap["statusCode"]; ok && statusCode != http.StatusOK {
return nil, errors.New(responseMap["message"].(string))
}

var chartData [][]float64
if err := json.Unmarshal(response, &chartData); err != nil {
return nil, err
}
return chartData, nil
}

func apiCall(url string) []byte {
req, _ := http.NewRequest("GET", url, nil)

req.Header.Add("accept", "application/json")
req.Header.Add("X-API-KEY", API_KEYS[rand.Intn(len(API_KEYS))])

res, _ := http.DefaultClient.Do(req)

defer res.Body.Close()

body, _ := io.ReadAll(res.Body)

return body
}
26 changes: 26 additions & 0 deletions crypto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package main

import (
"time"
)

type Crypto struct {
ID string
Name string
Symbol string
Price float64
LogoURL string
PriceChangePercentageDay float64
Description string
Categories []string
Website string
ATH float64
ATHDate time.Time
MarketCap float64
MarketCapRank int
DayHigh float64
DayLow float64
TotalSupply float64
CirculatingSupply float64
Volume float64
}
7 changes: 7 additions & 0 deletions currency.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

type Currency struct {
name string
rate float32
symbol string
}
31 changes: 31 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module github.com/judemont/tcoin

go 1.23.3

require github.com/charmbracelet/lipgloss v1.0.0

require (
github.com/charmbracelet/bubbles v0.20.0 // indirect
github.com/charmbracelet/bubbletea v1.2.2 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/lrstanley/bubblezone v0.0.0-20240914071701-b48c55a5e78e // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/text v0.20.0 // indirect
)

require (
github.com/NimbleMarkets/ntcharts v0.2.0
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/x/ansi v0.4.5 // indirect
github.com/guptarohit/asciigraph v0.7.3
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/sys v0.27.0 // indirect
)
53 changes: 53 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
github.com/NimbleMarkets/ntcharts v0.2.0 h1:uVpvUL9fZk/LGsc8E00kdBLHwh60llfvci+2JpJ6EDI=
github.com/NimbleMarkets/ntcharts v0.2.0/go.mod h1:BLzvdpQAv4NpGbOTsi3fCRzeDk276PGezkp75gD73kY=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
github.com/charmbracelet/bubbletea v1.2.2 h1:EMz//Ky/aFS2uLcKqpCst5UOE6z5CFDGRsUpyXz0chs=
github.com/charmbracelet/bubbletea v1.2.2/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM=
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41kof+qk=
github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM=
github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/guptarohit/asciigraph v0.7.3 h1:p05XDDn7cBTWiBqWb30mrwxd6oU0claAjqeytllnsPY=
github.com/guptarohit/asciigraph v0.7.3/go.mod h1:dYl5wwK4gNsnFf9Zp+l06rFiDZ5YtXM6x7SRWZ3KGag=
github.com/lrstanley/bubblezone v0.0.0-20240914071701-b48c55a5e78e h1:OLwZ8xVaeVrru0xyeuOX+fne0gQTFEGlzfNjipCbxlU=
github.com/lrstanley/bubblezone v0.0.0-20240914071701-b48c55a5e78e/go.mod h1:NQ34EGeu8FAYGBMDzwhfNJL8YQYoWZP5xYJPRDAwN3E=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
132 changes: 132 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package main

import (
"fmt"
"os"
"slices"
"time"

"github.com/NimbleMarkets/ntcharts/linechart/timeserieslinechart"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/table"
)

func main() {
var currency = Currency{name: "USD", rate: 1.0, symbol: "$"}
var cryptoSymbol string
if len(os.Args) <= 1 {
printHelp()
}

if slices.Contains(os.Args, "-h") || slices.Contains(os.Args, "--help") {
printHelp()
os.Exit(0)
}

cryptoSymbol = os.Args[1]

var cryptoData, err = getCoinData(cryptoSymbol)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

var cryptoChart, chartErr = getChart(cryptoSymbol, "6m")
if chartErr != nil {
fmt.Println(chartErr)
os.Exit(1)
}

printCoinData(cryptoData, cryptoChart, currency)
}

func printCoinData(coin Crypto, chartData [][]float64, currency Currency) {
var style = lipgloss.NewStyle().
Bold(true).
MarginTop(1)

fmt.Print(style.Render(coin.Name + " (" + coin.Symbol + "): "))

var priceColor string = "#067213"

if coin.PriceChangePercentageDay < 0 {
priceColor = "#ff0000"
}

style = lipgloss.NewStyle().
Background(lipgloss.Color(priceColor))

fmt.Println(style.Render(FormatPrice(currency.symbol, coin.Price)))

printChart(chartData)

dataTable := table.New().
Border(lipgloss.NormalBorder()).
BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("99"))).
Width(70).
StyleFunc(func(row, col int) lipgloss.Style {
if row%2 == 0 {
return lipgloss.NewStyle().
Align(lipgloss.Left).
MarginLeft(3).Bold(true)

} else {
return lipgloss.NewStyle().
Align(lipgloss.Left).
MarginLeft(3).
Bold(false)
}

})

dataTable.Row("Market Cap", FormatPrice(currency.symbol, coin.MarketCap))
dataTable.Row("Volume", FormatPrice(currency.symbol, coin.Volume))
dataTable.Row("Max supply", FormatPrice(coin.Symbol, coin.TotalSupply))
dataTable.Row("Circulating supply", FormatPrice(coin.Symbol, coin.CirculatingSupply))
dataTable.Row("% of supply in circulation", fmt.Sprintf("%.2f %%", (coin.CirculatingSupply/coin.TotalSupply)*100))
dataTable.Row("Homepage", coin.Website)

// style = lipgloss.NewStyle().
// Align(lipgloss.Center)

fmt.Println(dataTable.String())
}

func printChart(chart [][]float64) {
var minPrice float64 = chart[0][1]
var maxPrice float64 = 0

for i := 0; i < len(chart); i++ {
if chart[i][1] < minPrice {
minPrice = chart[i][1]
} else if chart[i][1] > maxPrice {
maxPrice = chart[i][1]
}
}

slc := timeserieslinechart.New(100, 15, timeserieslinechart.WithYRange(minPrice, maxPrice))

for i := 0; i < len(chart); i++ {
slc.Push(timeserieslinechart.TimePoint{Time: time.Unix(int64(chart[i][0]), 0), Value: chart[i][1]})
}

slc.DrawBraille()

fmt.Println(slc.View())
}

func printHelp() {
fmt.Println(
`
Usage: tcoin <crypto>
Options:
-h, --help Display this help message
Example: tcoin bitcoin
Example: tcoin eth
`)
os.Exit(0)

}
33 changes: 33 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import (
"fmt"
"strings"
)

func FormatPrice(symbol string, price float64) string {
priceStr := fmt.Sprintf("%f", price)
intPart, decPart := splitPrice(priceStr)
intPart = addCommas(intPart)
return fmt.Sprintf("%s.%s %s", intPart, decPart, symbol)
}

func splitPrice(price string) (string, string) {
parts := strings.Split(price, ".")
return parts[0], parts[1]
}

func addCommas(intPart string) string {
n := len(intPart)
if n <= 3 {
return intPart
}
var result strings.Builder
for i, digit := range intPart {
if i > 0 && (n-i)%3 == 0 {
result.WriteRune('\'')
}
result.WriteRune(digit)
}
return result.String()
}

0 comments on commit d1c21ab

Please sign in to comment.