Golang (heroku) で LINE Bot 作ってみる
Tags: line, bot, haskell, go
Published: 2020-03-26 02:24:38

---
title: Golang (heroku) で LINE Bot 作ってみる
published: true
description: 
tags: Line, bot, Haskell, Go 
---

**追記 (2016/04/12)**
Botの機能追加+ソースコードリファクタリングしてるうちにライブラリ化しました
https://github.com/dongri/line-bot-sdk-go
今のところ、text, sticker, image, video簡単に送れます。プロフィールも簡単に取得可能です。

---

まずコードから。メッセージを送るとそのメッセージをそのまま返すサンプル。下にちゃんとしたやつもあります。

```go

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"time"
)

// ReceivedMessage ...
type ReceivedMessage struct {
	Result []Result `json:"result"`
}

// Result ...
type Result struct {
	ID          string   `json:"id"`
	From        string   `json:"from"`
	FromChannel int      `json:"fromChannel"`
	To          []string `json:"to"`
	ToChannel   int      `json:"toChannel"`
	EventType   string   `json:"eventType"`
	Content     Content  `json:"content"`
}

// SendMessage ..
type SendMessage struct {
	To        []string `json:"to"`
	ToChannel int      `json:"toChannel"`
	EventType string   `json:"eventType"`
	Content   Content  `json:"content"`
}

// Content ...
type Content struct {
	ID          string   `json:"id"`
	ContentType int      `json:"contentType"`
	From        string   `json:"from"`
	CreatedTime int      `json:"createdTime"`
	To          []string `json:"to"`
	ToType      int      `json:"toType"`
	Text        string   `json:"text"`
}

// const ...
const (
	EndPoint  = "https://trialbot-api.line.me"
	ToChannel = 1383378250
	EventType = "138311608800106203"
)

func main() {
	http.HandleFunc("/", helloHandler)
	http.HandleFunc("/callback", callbackHandler)
	port := os.Getenv("PORT")
	addr := fmt.Sprintf(":%s", port)
	http.ListenAndServe(addr, nil)
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, LINE Bot")
}

func callbackHandler(w http.ResponseWriter, r *http.Request) {
	decoder := json.NewDecoder(r.Body)
	var m ReceivedMessage
	err := decoder.Decode(&m)
	if err != nil {
		log.Println(err)
	}
	apiURI := EndPoint + "/v1/events"
	for _, result := range m.Result {
		from := result.Content.From
		text := result.Content.Text
		content := new(Content)
		content.ContentType = result.Content.ContentType
		content.ToType = result.Content.ToType
		content.Text = text
		request(apiURI, "POST", []string{from}, *content)
	}
}

func request(endpointURL string, method string, to []string, content Content) {
	m := &SendMessage{}
	m.To = to
	m.ToChannel = ToChannel
	m.EventType = EventType
	m.Content = content
	b, err := json.Marshal(m)
	if err != nil {
		log.Print(err)
	}
	req, err := http.NewRequest(method, endpointURL, bytes.NewBuffer(b))
	if err != nil {
		log.Print(err)
	}
	req = setHeader(req)
	client := &http.Client{
		Transport: &http.Transport{Proxy: http.ProxyURL(getProxyURL())},
		Timeout:   time.Duration(30 * time.Second),
	}
	res, err := client.Do(req)
	if err != nil {
		log.Print(err)
	}
	defer res.Body.Close()

	var result map[string]interface{}
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Print(err)
	}
	if err := json.Unmarshal(body, &result); err != nil {
		log.Print(err)
	}
	log.Print(result)
}

func setHeader(req *http.Request) *http.Request {
	req.Header.Add("Content-Type", "application/json; charset=UTF-8")
	req.Header.Add("X-Line-ChannelID", os.Getenv("ChannelID"))
	req.Header.Add("X-Line-ChannelSecret", os.Getenv("ChannelSecret"))
	req.Header.Add("X-Line-Trusted-User-With-ACL", os.Getenv("MID"))
	return req
}

func getProxyURL() *url.URL {
	proxyURL, err := url.Parse(os.Getenv("ProxyURL"))
	if err != nil {
		log.Print(err)
	}
	return proxyURL
}
```

# やってるうちに詰まったところをまとめ

1. callback URLはhttpsのみ
今まで簡単なサンプルは自前のサーバーでやってて、当然https対応してない。しかしLINE Botのcallbackはhttpsにportを443に指定しないといけない。let's encryptでhttps化。しかしcallbackにリクエストが来ない。herokuでなんとかすると決める。

2. 許可したipしかsendmessageできない
herokuにデプロイしてログを見てるとipで弾かれてる。指定したipでしかpostできないみたい。bot管理ページにwhitelistがあってそこに自分のサーバーip追加しないといけないらしい。しかし、herokuはデプロイする度にipが変わってて使いものにならない。こちらの記事（ http://qiita.com/yuya_takeyama/items/0660a59d13e2cd0b2516 ）にもかいてあるようにproxyを使ってproxyのipを指定すれば解決できる。

3. contentTypeで弾かれてる
headerのcontent-typeかと思ったがメッセージcontentのcontentTypeをちゃんと指定しないといけない

4. 先着1万と書いてあるのが怪しい
1万枠外でもBotの登録できるんじゃないかと疑ってる。自分のアカウントは昨日早めに登録したので、使えるが、1アカウント1Botの制限のため今日嫁のアカウントで登録したけど、callbackが来ない。

とりあえず電車でも haskell

<img width="170" alt="Screen Shot 2016-04-08 at 11.21.08 PM.png" src="https://qiita-image-store.s3.amazonaws.com/0/15490/b5b4e023-c75f-b194-53f4-4bcff3ac766f.png">

![Screenshot_20160408-233023.png](https://qiita-image-store.s3.amazonaws.com/0/15490/085ff39a-eb68-3ab1-3d9f-5ce851e98f67.png)