[Go] CoinMarketCapのAPIを操作しよう

CoinMarketCapとは?
-> 急速に成長している仮想通貨スペースにおける世界で最も参照されている価格追跡ウェブサイトです。

まずDeveloper向けのアカウントを作成し、管理画面でAPI_KEYをコピーして、Developer向けページのIntroductionを一通り目を通します。
https://pro.coinmarketcap.com/account
https://coinmarketcap.com/api/documentation/v1/#section/Introduction

$ curl -H “X-CMC_PRO_API_KEY: ${API_KEY}” -H “Accept: application/json” -d “start=1&limit=5000&convert=USD” -G https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest

おおおおおおおおおおお、なんかすげえ

go

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
)

func main(){
	client := &http.Client{}
	req, err := http.NewRequest("GET", "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest", nil)
	if err != nil {
		log.Print(err)
		os.Exit(1)
	}

	q := url.Values{}
	q.Add("start", "1")
	q.Add("limit", "5000")
	q.Add("convert", "USD")

	req.Header.Set("Accepts", "application/json")
	req.Header.Add("X-CMC_PRO_API_KEY", "${API KEY}")
	req.URL.RawQuery = q.Encode()

	resp, err := client.Do(req);
	if err != nil {
		fmt.Println("Error!")
		os.Exit(1)
	}
	fmt.Println(resp.Status)
	respBody, _ := ioutil.ReadAll(resp.Body)
	fmt.Println(string(respBody));
}

取得する量が多すぎるな
1回のリクエストで25credit使うとなると、1日12回程度しかrequestできんな。流石にこれだと使い物にならんが、イメージは出来た。

[Go] Jsonで出力する

import (
	"encoding/json"
	"log"
	"net/http"
	"time"
)

func apiClockHandler(w http.ResponseWriter, r *http.Request){
	type ResponseBody struct {
		Time time.Time `json:"time"`
	}
	rb := &ResponseBody {
		Time: time.Now(),
	}

	w.Header().Set("Content-type", "application/json")

	if err := json.NewEncoder(w).Encode(rb); err != nil {
		log.Fatal(err)
	}
}

func main(){
	http.HandleFunc("/api/clock", apiClockHandler)
	log.Fatal(http.ListenAndServe(":8080",nil))
}

http://192.168.34.10:8080/api/clock

OK, ある程度のところまで来た

[Go] Bitflyerで取得した値をHTMLで表示する

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"html/template"
	"log"
	"fmt"
)

func bitflyerHandler(w http.ResponseWriter, r *http.Request){

	uri := "https://api.bitflyer.com/v1/getticker"
	req, _ := http.NewRequest("GET", uri, nil)

	client := new(http.Client)
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	byteArray, _ := ioutil.ReadAll(resp.Body)

	var ticker Ticker
	json.Unmarshal([]byte(byteArray),&ticker)


	t := template.Must(template.ParseFiles("/home/vagrant/go/src/github.com/me/sample/src/bitflyer.html.tpl"))
	if err := t.ExecuteTemplate(w, "bitflyer.html.tpl", fmt.Sprintf("%.0f\n", ticker.Ltp)); err != nil {
		log.Fatal(err)
	}
}


func main() {
	// fmt.Printf("%.0f\n",ticker.Ltp)

	http.HandleFunc("/bitflyer/", bitflyerHandler)
	log.Fatal(http.ListenAndServe(":8080",nil))
}


type Ticker struct {
	Code string `json:"product_code"`
	Ltp float64 `json:"ltp"`
}

bitflyer.html.tpl

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
</head>
<body>
	{{ . }} 
</body>
</html>

なるほど、これをcoin marketでやるか

[Go] サーバーを起動してHTMLファイルを表示

import (
	"log"
	"net/http"
)

func main(){
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("/home/vagrant/go/src/github.com/me/sample/src"))))
	log.Fatal(http.ListenAndServe(":8080",nil))
}

L /home/vagrant/go/src/github.com/me/sample/src にhtmlファイルを配置する

index.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
</head>
<body>
	Hello world
</body>
</html>

$ go run server.go

ほう、Echo使わなくても表示できるやん
これを動的に表示したい

### 動的に表示

import (
	"fmt"
	"log"
	"net/http"
	"time"
)

func clockHandler(w http.ResponseWriter, r *http.Request){
	fmt.Fprintf(w, `
		<!DOCTYPE html>
        <html>
        <body>
            It's %d o'clock now.
        </body>
        </html>
		`, time.Now().Hour())
}

func main(){
	http.HandleFunc("/clock/", clockHandler)
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("/home/vagrant/go/src/github.com/me/sample/src"))))
	log.Fatal(http.ListenAndServe(":8080",nil))
}

Printfで渡してるだけやな

### データの渡し方

func clockHandler(w http.ResponseWriter, r *http.Request){
	t := template.Must(template.ParseFiles("/home/vagrant/go/src/github.com/me/sample/src/clock.html.tpl"))

	if err := t.ExecuteTemplate(w, "clock.html.tpl", time.Now()); err != nil {
		log.Fatal(err)
	}
}

func main(){
	http.HandleFunc("/clock/", clockHandler)
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("/home/vagrant/go/src/github.com/me/sample/src"))))
	log.Fatal(http.ListenAndServe(":8080",nil))
}

echoでのtemplateと書き方はそんなに変わらんな

[Go] BitflyerのAPIを取得したい

### Bitflyerからの取得

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
	uri := "https://api.bitflyer.com/v1/gethealth"
	req, _ := http.NewRequest("GET", uri, nil)

	client := new(http.Client)
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	byteArray, _ := ioutil.ReadAll(resp.Body)
	fmt.Println(string(byteArray))
}

$ go run gethealth.go
{“status”:”NORMAL”}

### Jsonの扱い
GoでJsonの受け取り方
1. 構造体を定義

type User struct {
	Name string `json:"name"`
	Age int `json:"age"`
	Job string `json:"job"`
}
func main() {
	userStr :=
	 `{
	    "name": "tokumaru",
	    "age": 20,
	    "job": "student"
	  }`

	var user User
	json.Unmarshal([]byte(userStr),&user)

	fmt.Println(user.Name)
}

// 構造体を定義
type User struct {
	Name string `json:"name"`
	Age int `json:"age"`
	Job string `json:"job"`
}

$ go run test.go
tokumaru

↓上記をつなげる
### Bitcoinの価格を取得
bitcoinは小数点なので、float64で定義する。
表示する際は、fmt.Printf(“%.0f\n”,ticker.Ltp)として、小数点以下を切り捨てる

package main

import (
	"fmt"
	"encoding/json"
	"io/ioutil"
	"net/http"
)

func main() {
	uri := "https://api.bitflyer.com/v1/getticker"
	req, _ := http.NewRequest("GET", uri, nil)

	client := new(http.Client)
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	byteArray, _ := ioutil.ReadAll(resp.Body)

	var ticker Ticker
	json.Unmarshal([]byte(byteArray),&ticker)

	fmt.Printf("%.0f\n",ticker.Ltp)
}

// 構造体を定義
type Ticker struct {
	Code string `json:"product_code"`
	Ltp float64 `json:"ltp"`
}

$ go run test.go
5014562

これをHTMLで表示させたい

[Go] Ubuntu20.04にインストール

GoのDownloadsページから最新版のバージョンを確認します。
https://golang.org/dl/

今回はgo1.17.1

$ sudo wget -c https://dl.google.com/go/go1.17.1.linux-amd64.tar.gz -O – | sudo tar -xz -C /usr/local
$ export PATH=$PATH:/usr/local/go/bin
$ source ~/.profile
$ go version
go version go1.17.1 linux/amd64

うむ、OK

[Go] MySQLに接続する

まずMySQLにデータを作ります。
mysql
create database go;
use go
create table users (
id int auto_increment primary key,
name varchar(255),
age int,
address varchar(255),
update_at datetime
);
mysql> describe users;
+———–+————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+———–+————–+——+—–+———+—————-+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(255) | YES | | NULL | |
| age | int | YES | | NULL | |
| address | varchar(255) | YES | | NULL | |
| update_at | datetime | YES | | NULL | |
+———–+————–+——+—–+———+—————-+

package main

import (
	"fmt"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
)

func main(){
	_, err := sqlConnect()
	if err != nil {
		panic(err.Error())
	} else {
		fmt.Println("DB connect success!")
	}
}

func sqlConnect()(database *gorm.DB, err error){
	DBMS := "mysql"
	USER := "hoge"
	PASS := "fuga"
	PROTOCOL := "tcp(localhost:3306)"
	DBNAME := "go"

	CONNECT := USER + ":" + PASS + "@" + PROTOCOL + "/" + DBNAME + "?charset=utf8&parseTime=true&loc=Asia%2FTokyo"
	return gorm.Open(DBMS, CONNECT)
}

$ /home/vagrant/go/bin/dep ensure
$ go run main.go
DB connect success!

### データ挿入

import (
	"fmt"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
)

func main(){
	db, err := sqlConnect()
	if err != nil {
		panic(err.Error())
	}
	defer db.Close()

	error := db.Create(&Users{
		Name: "山田太郎",
		Age: 20,
		Address: "東京都港区赤坂1丁目1-1",
		UpdateAt: getDate(),
	}).Error
	if error != nil {
		fmt.Println(error)
	} else {
		fmt.Println("data insert success!")
	}

}

func sqlConnect()(database *gorm.DB, err error){
	DBMS := "mysql"
	USER := "hoge"
	PASS := "fuga"
	PROTOCOL := "tcp(localhost:3306)"
	DBNAME := "go"

	CONNECT := USER + ":" + PASS + "@" + PROTOCOL + "/" + DBNAME + "?charset=utf8&parseTime=true&loc=Asia%2FTokyo"
	return gorm.Open(DBMS, CONNECT)
}

type Users struct {
	ID int
	Name string `json:"name"`
	Age int `json:"age"`
	Address string `json:"address"`
	UpdateAt string `json:"updateAt" sql:"not null;type:date"`
}

func getDate() string {
	const layout = "2006-01-01 15:05:30"
	now := time.Now()
	return now.Format(layout)
}

$ go run main.go
data insert success!

mysql> select * from users;
+—-+————–+——+———————————+———————+
| id | name | age | address | update_at |
+—-+————–+——+———————————+———————+
| 1 | 山田太郎 | 20 | 東京都港区赤坂1丁目1-1 | 2021-09-09 14:23:20 |
+—-+————–+——+———————————+———————+
1 row in set (0.00 sec)

### データの取得

func main(){
	db, err := sqlConnect()
	if err != nil {
		panic(err.Error())
	}
	defer db.Close()

	result := []*Users{}
	error := db.Find(&result).Error
	if error != nil || len(result) == 0 {
		return
	}
	for _, user := range result {
		fmt.Println(user.Name)
	}
}

$ go run main.go
山田太郎

### データの更新

func main(){
	db, err := sqlConnect()
	if err != nil {
		panic(err.Error())
	}
	defer db.Close()

	result := []*Users{}
	error := db.Find(&result).Error
	if error != nil || len(result) == 0 {
		return
	}
	for _, user := range result {
		fmt.Println(user.Name)
	}

	fmt.Println("update")

	error = db.Model(Users{}).Where("id = ?", 1).Update(&Users{
		Name: "佐藤はじめ",
		UpdateAt: getDate(),
	}).Error

	if error != nil {
		fmt.Println(error)
	}

	result = []*Users{}
	db.Find(&result)
	for _, user := range result {
		fmt.Println(user.Name)
	}
}

$ go run main.go
山田太郎
update
佐藤はじめ

### DELETE

	fmt.Println("Delete")

	error = db.Where("id = ?", 1).Delete(Users{}).Error
	

	if error != nil {
		fmt.Println(error)
	}

	result = []*Users{}
	db.Find(&result)
	for _, user := range result {
		fmt.Println(user.Name)
	}

$ go run main.go
佐藤はじめ
Delete

mysql> select * from users;
Empty set (0.00 sec)

OK、CRUDは一通りわかった🈴

[Go] HTMLテンプレートを使って表示

public/view/hello.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
</head>
<body>
	{{define "hello"}}Hello, {{.}}!{{end}}
</body>
</html>

main.go

import (
	"html/template"
	"io"
	"net/http"

	"github.com/labstack/echo"
)

type Template struct {
	templates *template.Template
}

func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
	return t.templates.ExecuteTemplate(w, name, data)
}

func Hello(c echo.Context) error {
	return c.Render(http.StatusOK, "hello", "World")
}

func main(){
	e := echo.New()
	t := &Template{
		templates: template.Must(template.ParseGlob("public/views/*.html")),
	}
	e.Renderer = t
	e.GET("/hello", Hello)

	e.Logger.Fatal(e.Start(":8000"))
}

うーむ、出来たのはわかったけど、イマイチ仕組みがわからん

[Go] Anacondaでtwitterのつぶやき取得

/home/vagrant/go/bin/dep init

twitterのconsumer key, access-tokenなどを用意します。

package main

import (
	"github.com/ChimeraCoder/anaconda"
)

func main() {
    anaconda.NewTwitterApiWithCredentials("your-access-token", "your-access-token-secret", "your-consumer-key", "your-consumer-secret")
}

$ /home/vagrant/go/bin/dep ensure

作成されています。

twitterAccount.json

{
  "accessToken": "",
  "accessTokenSecret": "",
  "consumerKey": "",
  "consumerSecret": ""
}
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"

	"github.com/ChimeraCoder/anaconda"
)

func main(){

	raw, error := ioutil.ReadFile("twitterAccount.json")

	if error != nil {
		fmt.Println(error.Error())
		return
	}

	var twitterAccount TwitterAccount
	json.Unmarshal(raw, &twitterAccount)

	api := anaconda.NewTwitterApiWithCredentials(twitterAccount.AccessToken, twitterAccount.AccessTokenSecret, twitterAccount.ConsumerKey, twitterAccount.ConsumerSecret)

	searchResult, _ := api.GetSearch(`スタートアップ`, nil)
	for _, tweet := range searchResult.Statuses {
		fmt.Println(tweet.Text)
	}
}

type TwitterAccount struct {
	AccessToken string `json:"accessToken"`
	AccessTokenSecret string `json:"accessTokenSecret"`
	ConsumerKey string `json:"consumerKey"`
	ConsumerSecret string `json:"consumerSecret"`
}

$ go run main.go
“厄介者”火山灰で排水処理を手助け、
巨大企業も目?
RT @ecoecoecho: 何をやっているか全く分からないが藤原竜也のイ
RT @Herlipto_info: 𝖲𝗁𝖺𝗋𝗂𝗇𝗀 𝗈𝗎𝗋 𝗅𝗈𝗏𝖾𝗅𝗒 𝗇𝖾𝗐 𝗉𝗂𝖾𝖼𝖾𝗌.

9/20(mon)20:00
RT @Herlipto_info: 𝖲𝗁𝖺𝗋𝗂𝗇𝗀 𝗈𝗎𝗋 𝗅𝗈𝗏𝖾𝗅𝗒 𝗇𝖾𝗐 𝗉𝗂𝖾𝖼𝖾𝗌.

// 省略

おおおおおおおおおおお

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/labstack/echo"
	"github.com/ChimeraCoder/anaconda"
)

func main(){

	e := echo.New()
	e.Post("/tweet", search)
	e.Logger.Fatal(e.start(":1323"))
}

func search(c echo.Context) error {
	keyword := c.FormValue("keyword")
	api := connectTwitterApi()

	searchResult, _ := api.GetSearch(`"` +keyword+ `"`, nil)

	tweets := make([]*Tweet, 0)

	for _, data := range searchResult.Statuses {
		tweet := new(Tweet)
		tweet.Text = data.FullText
		tweet.User = data.User.Name

		tweets = append(tweets, tweet)
	}

	return c.JSON(http.StatusOK, tweets)
}

func connectTwitterApi() *anaconda.TwitterApi {
	raw, error := ioutil.ReadFile("twitterAccount.json")

	if error != nil {
		fmt.Println(error.Error())
		return
	}

	var twitterAccount TwitterAccount
	json.Unmarshal(raw, &twitterAccount)

	return anaconda.NewTwitterApiWithCredentials(twitterAccount.AccessToken, twitterAccount.AccessTokenSecret, twitterAccount.ConsumerKey, twitterAccount.ConsumerSecret)

}



type TwitterAccount struct {
	AccessToken string `json:"accessToken"`
	AccessTokenSecret string `json:"accessTokenSecret"`
	ConsumerKey string `json:"consumerKey"`
	ConsumerSecret string `json:"consumerSecret"`
}

type Tweet struct {
	User string `json:"user"`
	Text string `json:"text"`
}

type Tweets *[]Tweet

$ go build
go: inconsistent vendoring in /home/vagrant/go/src/github.com/me/twitter:
github.com/ChimeraCoder/anaconda@v2.0.0+incompatible: is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt
github.com/ChimeraCoder/tokenbucket@v0.0.0-20131201223612-c5a927568de7: is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt
github.com/azr/backoff@v0.0.0-20160115115103-53511d3c7330: is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt
github.com/dustin/go-jsonpointer@v0.0.0-20160814072949-ba0abeacc3dc: is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt
github.com/dustin/gojson@v0.0.0-20160307161227-2e71ec9dd5ad: is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt
github.com/garyburd/go-oauth@v0.0.0-20180319155456-bca2e7f09a17: is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt
github.com/labstack/echo/v4@v4.5.0: is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt
golang.org/x/net@v0.0.0-20210917221730-978cfadd31cf: is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt

run ‘go mod vendor’ to sync, or use -mod=mod or -mod=readonly to ignore the vendor directory

なんでやろ
まあ 取得できるって事まではわかった。

[Go] Echoでルーティングを実装

### Get

import (
	"net/http"
	"github.com/labstack/echo/v4"
)

func main() {
	e := echo.New()
	e.GET("/users/:name", getUserName)

	e.Logger.Fatal(e.Start(":1323"))
}

func getUserName(c echo.Context) error {
	name := c.Param("name")
	return c.JSON(http.StatusOK, name)
}

### QueryParamを使う場合

func main() {
	e := echo.New()
	e.GET("/show", show)

	e.Logger.Fatal(e.Start(":1323"))
}

func show(c echo.Context) error {
	team := c.QueryParam("team")
	member := c.QueryParam("member")
	return c.String(http.StatusOK, "team:"+team+", member:"+member)
}

http://192.168.33.10:1323/show?team=test&member=hpscript

### Post
FormValueで受け取る

func main() {
	e := echo.New()
	e.GET("/save", save)

	e.Logger.Fatal(e.Start(":1323"))
}

func save(c echo.Context) error {

	name := c.FormValue("name")
	email := c.FormValue("email")
	return c.String(http.StatusOK, "name:"+name+", email:"+email)
}

### JSONで返却

type User struct {
	Name string `json:"name"`
	Email string `json:"email"`
}

func main() {
	e := echo.New()
	e.GET("/users", saveUser)

	e.Logger.Fatal(e.Start(":1323"))
}

func saveUser(c echo.Context) error {

	u := new(User)
	if err := c.Bind(u); err != nil {
		return err
	}
	return c.JSON(http.StatusOK, u)
}
type Message struct {
	Name string `json:"name"`
	Email string `json:"email"`
	Message string `json:"message"`
}

type Response struct {
	Name string `json:"name"`
	Email string `json:"email"`
	Message string `json:message`
	Status string `json:status`
}

func main() {
	e := echo.New()
	e.GET("/send", sendMessage)

	e.Logger.Fatal(e.Start(":1323"))
}

func sendMessage(c echo.Context) error {

	m := new(Message)
	if error := c.Bind(m); error != nil {
		return error
	}
	r := new(Response)
	r.Name = m.Name
	r.Email = m.Email
	r.Message = m.Message
	r.Status = "success"
	return c.JSON(http.StatusOK, r)
}

なるほど、ルーティングの機能についてはある程度わかりました。
MVCに分かれているのではなく、1枚のファイルの中に書いていくのね。