본문 바로가기

좋아하는 것_매직IT/7.gin

Coinpaprika Openapi 와 텔레그램 봇을 활용한 가상화폐 검색 기능 간단 구현!

반응형

블로그 목적 

Coinpaprika Openapi 와 텔레그램 봇을 활용한 가상화폐 검색 기능 구현에 대해서 공부및 정리후 나만의 노하우와 지식을 공유한다.

블로그 요약

1. Coinpaprika Openapi 에 대해서 알아본다.
2. Coinpaprika Openapi 와 텔레그램 봇을 활용한 가상화폐 검색 기능을 간단하게 구현해 본다. 

블로그 상세내용

요즘에 재테크로 가상화폐에 대해서 관심이 생겨서 가상화폐관련 Openapi 가 있나? 검색창을 두리번 거리다가요.
Coinpaprika  Openapi 를 발견했습니다. 
해당 Coinpaprika  Openapi 를 알아보고 어떤 기능을 개인적으로 구현해 볼까? 고민하던 중 ..
텔레그램 봇을 활용해서 제가 검색하고자 하는 가상화폐 정보를 전송해보면 어떨까? 해서
바로 Coinpaprika  Openapi 를 공부해봅니다.

Coinpaprika  Openapi 에 대해서 공부를 하기위해서 우선 Coinpaprika  사이트에 접속합니다. 

https://coinpaprika.com/

상단에 API 로 빨간 버튼을 클릭하시면 아래와 같이 Openapi 사이트로 접속하실 수 있습니다. 

그리고 Documentation 를 클릭하시면 아래와 같이 무료로 제공하는 Openapi 를 알게되었네요.

당연히 무료 버전은 제한이 있겠죠? 가격정책을 알아보니 아래와 같습니다. 
한달에 20000 call 정도를 허용하며 Assets 허용도 2000이고 이외에 여러가지 제약사항이 있지만 저는 테스트 용도이기때문에 한번 무료버전을 통해서 구현해볼 마음을 가지게 됩니다. 

그리고 아래와 같이 인기있는 프로그래밍 언어에 대해서 API 클라이언트를 제공하더라고요

저는 Golang 을 사용해서 구현할거기 때문에 GO를 클릭해봅니다. 아래와 같이 github 사이트로 이동하게 되고요.

Readme 를 보니 아래와 같이 샘플 코드도 제공하고 있네요.

그리고 어떤 openapi 를 활용할까? 찾아보니 아래와 같이 가상화폐 id 로 조회할 수 있는 openapi 가 있었습니다. 

샘플 URI 테스트 정보

그럼, 위에서 찾아본 내용을 기반으로 해서 구현할 go 프로젝트를 세팅해 봅니다. 
참고로 웹프레임워크는 go 에서 나름 유명한 gin 프레임워크를 활용하겠습니다. 

$ go mod init coinpaprika_telegram
$ go get -u github.com/gin-gonic/gin

그리고 텔레그램을 활용할 예정이므로 아래와 같이 세팅합니다. 

$ go get -u github.com/go-telegram-bot-api/telegram-bot-api

아무튼, 저는  Coinpaprika Openapi 를 공부해서 테스트도 해보고 파라미터들도 눈여겨보면서 어떻게 구현할지 머릿속에 정리해봅니다. 

그리고 머리속에 정리한 내용을 토대로 제가 구해야할 함수는 아래와 같이 2가지 정도를 구현하면 될것 같다는 생각이 듭니다. 

우선, coin keyword 를 coin id 로 바꿀 기능이 필요할것 같고 coin id 를 토대로 가상화폐 정보를 가져올 함수를 작성합니다. 
(물론 현재 구현상으로는 golang 코드에 코인매칭정보가 하드코딩이 되어 있는데요. 하드코딩을 제거하려면 redis 를 사용해도 될것으로 보이네요. 예를들어 coin list 를 받아와서 가공및 코인매칭 정보를 저장해 두면 go 코드에 하드코딩된 부분을 제거하고, redis 조회하는 기능을 통해 해결 할 수 있을것으로 보이네요.)


둘, 위에서 가져온 가상화폐정보를 기반으로 필요정보들을 메시지로 만들고 텔레그램으로 전송하는 함수를 작성하면 될것 같습니다. 

그래서 아래와 같이 함수를 구현합니다. 

func fetchTickerDetails(keyword string) (TickerInfo, error) {
	var tickerInfo TickerInfo

	// 하드코딩된 map 데이터
	coinData := map[string]struct {
		ID     string
		Ticker string
	}{
		"비트코인":  {"btc-bitcoin", "BTC"},
		"이더리움": {"eth-ethereum", "ETH"},
		"리플":   {"xrp-xrp", "XRP"},
		"도지코인": {"doge-dogecoin", "DOGE"},
	}

	// 하드코딩된 데이터에서 해당 코인 symbol 찾기
	coin, exists := coinData[keyword]
	if !exists {
		return TickerInfo{}, fmt.Errorf("symbol not found: %s", keyword)
	}

	// API 호출
	url := fmt.Sprintf("https://api.coinpaprika.com/v1/tickers/%s?quotes=KRW", coin.ID)
	resp, err := http.Get(url)
	if err != nil {
		return TickerInfo{}, fmt.Errorf("failed to make the request: ticker[%s] %v", coin.Ticker, err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return TickerInfo{}, fmt.Errorf("failed to get valid response: ticker[%s] %s", coin.Ticker, resp.Status)
	}

	if err := json.NewDecoder(resp.Body).Decode(&tickerInfo); err != nil {
		return TickerInfo{}, fmt.Errorf("failed to parse JSON: %v", err)
	}

	return tickerInfo, nil
}

그리고 필요정보들을 메시지로 만드는 함수를 구현해봅니다.

종목, 기호, 가격, 시가총액, 거래량, 변동 등등에 대해서 정보를 넣어봤습니다.

func formatTickerDetails(tickerinfo TickerInfo) string {
	coininfo := tickerinfo.CoinInfo["KRW"]
	return fmt.Sprintf(
		"종목: %s\n"+
			"기호: %s\n"+
			"가격(KRW): %.2f원\n"+
			"시가총액: %.2f T\n"+
			"거래량(24H): %.2f T\n"+
			"변동(24H): %.2f퍼센트\n"+
			"변동(7D): %.2f퍼센트\n",
		tickerinfo.Name,
		tickerinfo.Symbol,
		coininfo.Price,
		coininfo.MarketCap/1e12, // Convert to Trillion
		coininfo.Volume24h/1e12, // Convert to Trillion
		coininfo.Change24h,
		coininfo.Change7d,
	)
}

아래는 gin 프레임워크를 활용한 전체 코드입니다. 

package main

import (
	"net/http"
    "encoding/json"
    "fmt"
    "github.com/gin-gonic/gin"   
    tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)

const telegramBotToken = "텔레그램봇 토큰"
const telegramChannelID = "텔레그램 채널ID" 

type RequestData struct {
    Msg string `json:"msg"`
    SenderName string `json:"sender_name"`
}

type CoinInfo struct {
	Price     float64 `json:"price"`
	MarketCap float64 `json:"market_cap"`
	Volume24h float64 `json:"volume_24h"`
	Change24h float64 `json:"percent_change_24h"`
	Change7d  float64 `json:"percent_change_7d"`
}

type TickerInfo struct {
	ID       string              `json:"id"`
	Name     string              `json:"name"`
	Symbol   string              `json:"symbol"`
	CoinInfo map[string]CoinInfo `json:"quotes"`
}

func coinSearch(requestData *RequestData) string {

	message := "오늘의 가상화폐 정보가 없네요."
	keyword := requestData.Msg

	var title string
	title = fmt.Sprintf("[오늘의 가상화폐(%s)]", keyword)

	details, err := fetchTickerDetails(keyword)
	if err != nil {
		fmt.Printf("Error fetching ticker details: %v", err)
		return message
	}

	message = formatTickerDetails(details)
	message = fmt.Sprintf("%s\n%s", title, message)

	return message
}


func fetchTickerDetails(keyword string) (TickerInfo, error) {
	var tickerInfo TickerInfo

	// 하드코딩된 map 데이터
	coinData := map[string]struct {
		ID     string
		Ticker string
	}{
		"비트코인":  {"btc-bitcoin", "BTC"},
		"이더리움": {"eth-ethereum", "ETH"},
		"리플":   {"xrp-xrp", "XRP"},
		"도지코인": {"doge-dogecoin", "DOGE"},
	}

	// 하드코딩된 데이터에서 해당 코인 symbol 찾기
	coin, exists := coinData[keyword]
	if !exists {
		return TickerInfo{}, fmt.Errorf("symbol not found: %s", keyword)
	}

	// API 호출
	url := fmt.Sprintf("https://api.coinpaprika.com/v1/tickers/%s?quotes=KRW", coin.ID)
	resp, err := http.Get(url)
	if err != nil {
		return TickerInfo{}, fmt.Errorf("failed to make the request: ticker[%s] %v", coin.Ticker, err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return TickerInfo{}, fmt.Errorf("failed to get valid response: ticker[%s] %s", coin.Ticker, resp.Status)
	}

	if err := json.NewDecoder(resp.Body).Decode(&tickerInfo); err != nil {
		return TickerInfo{}, fmt.Errorf("failed to parse JSON: %v", err)
	}

	return tickerInfo, nil
}

func formatTickerDetails(tickerinfo TickerInfo) string {
	coininfo := tickerinfo.CoinInfo["KRW"]
	return fmt.Sprintf(
		"종목: %s\n"+
			"기호: %s\n"+
			"가격(KRW): %.2f원\n"+
			"시가총액: %.2f T\n"+
			"거래량(24H): %.2f T\n"+
			"변동(24H): %.2f퍼센트\n"+
			"변동(7D): %.2f퍼센트\n",
		tickerinfo.Name,
		tickerinfo.Symbol,
		coininfo.Price,
		coininfo.MarketCap/1e12, // Convert to Trillion
		coininfo.Volume24h/1e12, // Convert to Trillion
		coininfo.Change24h,
		coininfo.Change7d,
	)
}

func sendToTelegram(message string) error {

	bot, err := tgbotapi.NewBotAPI(telegramBotToken)
	if err != nil {
		return err
	}

	msg := tgbotapi.NewMessage(telegramChannelID, message)
	msg.ParseMode = "HTML"

	_, err = bot.Send(msg)

	return err
}

func coinHandler(c *gin.Context) {
	requestData := &RequestData{}

	if err := c.ShouldBindJSON(&requestData); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"code": 200, "msg": "JSON data parsing error"})
		fmt.Printf("JSON data parsing error:", err)
		return
	}
    

    var message string
    message = coinSearch(requestData)

    sendErr := sendToTelegram(message)
    if sendErr != nil {
        fmt.Printf("Telegram Message send failed:", sendErr)
        return
    }

	c.JSON(http.StatusOK, gin.H{"code": 100, "msg": "success"})

}

func main() {
    r := gin.New()

    r.POST("/coin", coinHandler)

    go func() {
        err := r.Run(":" + "18082")
        if err != nil {
            fmt.Printf("Failed to start server:", err)
        }
    }()

    select {}
}

그리고 insomnia 툴을 사용해서 테스트 요청을 전송합니다. (저는 비트코인과 이더리움에 관심이 있어서 검색해 봅니다.)

msg : 비트코인
sender_name : 매직

msg : 이더리움
sender_name : 매직

그러면 위와 같이 정상적으로 전송했다는 200 OK 코드를 받게되고요...

아래와 같이 텔레그램봇이 있는 방으로 메시지가 수신됩니다. 

 

여기까지가 제가 한번 Coinpaprika Openapi  를 활용해서 golang 과 gin 프레임워크 으로 구현한 가상화폐정보 검색 기능입니다. 

항상 믿고 봐주셔서 감사합니다. 

 

728x90
300x250