├── LICENSE ├── README.md ├── example ├── example └── main.go └── lbot ├── bot.go ├── config.go ├── const.go ├── main.go └── main_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Taiwan Intelligent Home Crop. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-line-bot 2 | Golang Line Bot, not official 3 | 4 | ## Usage 5 | 6 | go get github.com/tihtw/go-line-bot/lbot 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/example: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tihtw/go-line-bot/8ab059d514877ac1d770be47ac519aa7488e6b9e/example/example -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/tihtw/go-line-bot/lbot" 6 | "log" 7 | "net/http" 8 | "os" 9 | ) 10 | 11 | var bot lbot.Bot 12 | 13 | func main() { 14 | fmt.Println("Server Start!!") 15 | var config lbot.Config 16 | config.SetDefaults() 17 | config.ChannelID = os.Getenv("LINE_CHANNEL_ID") 18 | config.ChannelSecret = os.Getenv("LINE_CHANNEL_SECRET") 19 | config.MID = os.Getenv("LINE_MID") 20 | config.Debug = true 21 | 22 | bot.SetConfig(config) 23 | http.HandleFunc("/", index) 24 | fmt.Println("Hello") 25 | fmt.Println(lbot.EventMessage) 26 | err := http.ListenAndServe(":8081", nil) 27 | if err != nil { 28 | fmt.Println(err.Error()) 29 | return 30 | } 31 | 32 | } 33 | 34 | func index(w http.ResponseWriter, r *http.Request) { 35 | log.Println("Received Message") 36 | req, err := lbot.ParseRequest(r) 37 | if err != nil { 38 | log.Println(err) 39 | return 40 | } 41 | 42 | profiles, _ := bot.GetUserProfile(req.Result[0].Content.From) 43 | if req.Result[0].EventType == lbot.EventOperation { 44 | 45 | } else { 46 | contentType := req.Result[0].Content.ContentType 47 | switch contentType { 48 | case lbot.TextMessage: 49 | log.Printf("User %v -> Bot: %v\n", profiles[0].DisplayName, req.Result[0].Content.Text) 50 | receivedMsg := req.Result[0].Content.Text 51 | bot.SendTextMessage(req.Result[0].Content.From, receivedMsg) 52 | log.Printf("Bot -> User %v: %v", profiles[0].DisplayName, receivedMsg) 53 | case lbot.ImageMessage: 54 | log.Printf("User %v -> Bot: Image id : %v\n", profiles[0].DisplayName, req.Result[0].Content.Id) 55 | case lbot.VideoMessage: 56 | log.Printf("User %v -> Bot: Video id : %v\n", profiles[0].DisplayName, req.Result[0].Content.Id) 57 | case lbot.AudioMessage: 58 | log.Printf("User %v -> Bot: Audio id : %v\n", profiles[0].DisplayName, req.Result[0].Content.Id) 59 | case lbot.LocationMessage: 60 | log.Printf("User %v -> Bot: Location %v %v (%v, %v)\n", profiles[0].DisplayName, 61 | req.Result[0].Content.Location.Title, 62 | req.Result[0].Content.Location.Address, 63 | req.Result[0].Content.Location.Latitude, 64 | req.Result[0].Content.Location.Longitude) 65 | case lbot.StickerMessage: 66 | log.Printf("User %v -> Bot: Stiker %v\n", profiles[0].DisplayName, 67 | buildStickerUrl( 68 | lbot.Sticker{ 69 | Stkver: req.Result[0].Content.ContentMetadata.Stkver, 70 | Stkpkgid: req.Result[0].Content.ContentMetadata.Stkpkgid, 71 | Stkid: req.Result[0].Content.ContentMetadata.Stkid})) 72 | 73 | default: 74 | log.Printf("Unsupport content type: %v from %v", contentType, req.Result[0].Content.From) 75 | 76 | } 77 | } 78 | 79 | // log.Printf("User %v: %v\n", req.Result[0].Content.From, req.Result[0].Content.Text) 80 | 81 | // log.Printf("%v: %v\n", profiles[0].DisplayName, req) 82 | 83 | } 84 | 85 | func buildStickerUrl(s lbot.Sticker) string { 86 | return "http://dl.stickershop.line.naver.jp/products/0/0/" + s.Stkver + "/" + s.Stkpkgid + "/PC/stickers/" + s.Stkid + ".png" 87 | } 88 | -------------------------------------------------------------------------------- /lbot/bot.go: -------------------------------------------------------------------------------- 1 | package lbot 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | func (b *Bot) SetConfig(c Config) { 13 | b.config = &c 14 | } 15 | 16 | func (b *Bot) SendTextMessage(m mid, s string) error { 17 | if b.config == nil { 18 | return errors.New("Config have not been set") 19 | } 20 | if b.config.Debug { 21 | log.Println("Start to Set Message") 22 | } 23 | 24 | var payload Request 25 | payload.SetDefaults() 26 | payload.SetText(s) 27 | payload.AddTargetUser(mid(m)) 28 | 29 | out, err := json.Marshal(payload) 30 | if err != nil { 31 | return err 32 | } 33 | if b.config.Debug { 34 | log.Println("Output json: " + string(out)) 35 | } 36 | 37 | req, err := http.NewRequest("POST", b.config.ServerHost+"/v1/events", strings.NewReader(string(out))) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | b.addAuthHeader(req) 43 | resp, err := http.DefaultClient.Do(req) 44 | if err != nil { 45 | return err 46 | } 47 | result, err := ioutil.ReadAll(resp.Body) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | if b.config.Debug { 53 | log.Println("Result: ", string(result)) 54 | } 55 | 56 | return nil 57 | } 58 | 59 | func (b *Bot) SendImageMessage(m mid, originalContentUrl, previewImageUrl string) error { 60 | if b.config == nil { 61 | return errors.New("Config have not been set") 62 | } 63 | if b.config.Debug { 64 | log.Println("Start to Set Message") 65 | } 66 | 67 | var payload Request 68 | payload.SetDefaults() 69 | payload.SetImage(originalContentUrl, previewImageUrl) 70 | payload.AddTargetUser(mid(m)) 71 | 72 | out, err := json.Marshal(payload) 73 | if err != nil { 74 | return err 75 | } 76 | if b.config.Debug { 77 | log.Println("Output json: " + string(out)) 78 | } 79 | 80 | req, err := http.NewRequest("POST", b.config.ServerHost+"/v1/events", strings.NewReader(string(out))) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | b.addAuthHeader(req) 86 | resp, err := http.DefaultClient.Do(req) 87 | if err != nil { 88 | return err 89 | } 90 | result, err := ioutil.ReadAll(resp.Body) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | if b.config.Debug { 96 | log.Println("Result: ", string(result)) 97 | } 98 | 99 | return nil 100 | } 101 | 102 | func (b *Bot) GetUserProfile(m mid) ([]ProfileInfo, error) { 103 | if b.config == nil { 104 | return nil, errors.New("Config have not been set") 105 | } 106 | 107 | if b.config.Debug { 108 | log.Println("Start to Set Message") 109 | } 110 | 111 | req, err := http.NewRequest("GET", b.config.ServerHost+"/v1/profiles?mids="+string(m), nil) 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | b.addAuthHeader(req) 117 | resp, err := http.DefaultClient.Do(req) 118 | if err != nil { 119 | return nil, err 120 | } 121 | result, err := ParseProfileResponse(resp) 122 | if err != nil { 123 | return nil, err 124 | } 125 | 126 | return result.Contacts, nil 127 | 128 | } 129 | 130 | func (b *Bot) addAuthHeader(r *http.Request) { 131 | 132 | r.Header.Set("Content-type", "application/json; charset=UTF-8") 133 | r.Header.Set("X-Line-ChannelID", b.config.ChannelID) 134 | r.Header.Set("X-Line-ChannelSecret", b.config.ChannelSecret) 135 | r.Header.Set("X-Line-Trusted-User-With-ACL", b.config.MID) 136 | 137 | } 138 | -------------------------------------------------------------------------------- /lbot/config.go: -------------------------------------------------------------------------------- 1 | package lbot 2 | 3 | // A config for set LINE service config 4 | type Config struct { 5 | ChannelID string 6 | ChannelSecret string 7 | MID string 8 | ServerHost string 9 | Debug bool 10 | } 11 | 12 | const ( 13 | DefaultServerHost = "https://trialbot-api.line.me" 14 | ) 15 | 16 | func (c *Config) SetDefaults() { 17 | c.ServerHost = DefaultServerHost 18 | c.Debug = false 19 | } 20 | 21 | func NewConfig() *Config { 22 | return &Config{} 23 | } 24 | -------------------------------------------------------------------------------- /lbot/const.go: -------------------------------------------------------------------------------- 1 | package lbot 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | const ( 8 | EventSendMessage = eventString("138311608800106203") 9 | EventMessage = eventString("138311609000106303") 10 | EventOperation = eventString("138311609100106403") 11 | 12 | TextMessage = 1 13 | ImageMessage = 2 14 | VideoMessage = 3 15 | AudioMessage = 4 16 | LocationMessage = 7 17 | StickerMessage = 8 18 | ContactMessage = 10 19 | 20 | OpAddedFriend = 4 21 | OpBlocked = 8 22 | 23 | ToTypeUser = 1 24 | 25 | DefaultToChannel = 1383378250 26 | ) 27 | 28 | var ( 29 | ErrUserExceed = errors.New("User exceed limition") 30 | ) 31 | -------------------------------------------------------------------------------- /lbot/main.go: -------------------------------------------------------------------------------- 1 | package lbot 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | "encoding/json" 7 | // "errors" 8 | "io/ioutil" 9 | "net/http" 10 | // "strings" 11 | "time" 12 | ) 13 | 14 | // The LINE BOT 15 | type Bot struct { 16 | // Configuration of this BOT 17 | config *Config 18 | } 19 | 20 | // mid, message? ID 21 | type mid string 22 | 23 | type eventString string 24 | 25 | type Location struct { 26 | Title string `json:"title"` 27 | Address string `json:"Address,omitempty"` 28 | Latitude float64 `json:"latitude"` 29 | Longitude float64 `json:"longitude"` 30 | } 31 | 32 | type imageContent struct { 33 | OriginalContentUrl string `json:"originalContentUrl,omitempty"` 34 | PreviewImageUrl string `json:"previewImageUrl,omitempty"` 35 | } 36 | 37 | type contentMetadata struct { 38 | Stkid string `json:"STKID"` 39 | Stkpkgid string `json:"STKPKGID"` 40 | Stkver string `json:"Stkver"` 41 | } 42 | 43 | type Sticker struct { 44 | Stkid string 45 | Stkpkgid string 46 | Stkver string 47 | } 48 | 49 | // When a user sends a message, the following data is sent to your server from the LINE platform. 50 | type Message struct { 51 | 52 | // Identifier of the message. 53 | Id string `json:"id,omitempty"` 54 | // A numeric value indicating the type of message sent. 55 | ContentType int `json:"contentType,omitempty"` 56 | // MID of the user who sent the message. 57 | From mid `json:"from,omitempty"` 58 | // Time and date request created. Displayed as the amount of time passed since 0:00:00 on January 1, 1970. The unit is given in milliseconds. 59 | CreatedTime int `json:"createdTime,omitempty"` 60 | parsedCreatedTime *time.Time 61 | 62 | // Array of user who will receive the message. 63 | To []mid `json:"to,omitempty"` 64 | // Type of user who will receive the message. (1: To user ) 65 | ToType int `json:"toType,omitempty"` 66 | // Detailed information about the message 67 | ContentMetadata *contentMetadata `json:"contentMetadata,omitempty"` 68 | 69 | // Posted text to be delivered. Note: users can send a message which has max 10,000 characters. 70 | Text string `json:"text,omitempty"` 71 | imageContent 72 | // Location data. This property is defined if the text message sent contains location data. 73 | Location *Location `json:"location,omitempty"` 74 | } 75 | 76 | // The LINE platform sends operation requests to your BOT API server when users perform actions such as adding your official account as friend. 77 | type Operation struct { 78 | 79 | // Revision number of operation 80 | Revision int `json:"revision,omitempty"` 81 | // Type of operation 82 | OpType int `json:"opType,omitempty"` 83 | // Array of MIDs 84 | Params []*string `json:"params,omitempty"` 85 | } 86 | 87 | type Content struct { 88 | Message 89 | Operation 90 | } 91 | 92 | type Result struct { 93 | // Fixed value "u2ddf2eb3c959e561f6c9fa2ea732e7eb8" 94 | From mid `json:from` 95 | // Fixed value "1341301815" 96 | FromChannel json.Number `json:"fromChannel"` 97 | // MID value granted by the BOT API server’s Channel 98 | To []mid `json:"to"` 99 | // Channel ID of the BOT API server 100 | ToChannel json.Number `json:"toChannel"` 101 | // Identifier used to show the type of data 102 | EventType eventString `json:eventType` 103 | // ID string to identify each event 104 | Id string `json:"id"` 105 | // Actual data relayed by the message 106 | Content Content `json:"content"` 107 | } 108 | 109 | type Request struct { 110 | 111 | // Array of target user. Max count: 150. 112 | To []mid `json:"to"` 113 | // 1383378250 Fixed value 114 | ToChannel int `json:"toChannel"` 115 | // "138311608800106203" Fixed value. 116 | EventType eventString `json:"eventType"` 117 | // Object that contains the message (varies according to message type). 118 | Content Content `json:"content"` 119 | } 120 | 121 | // Return object for Callback Request 122 | type CallbackRequest struct { 123 | vaild bool 124 | Result []Result `json:"result"` 125 | } 126 | 127 | type UserProfileResponse struct { 128 | // contacts 129 | Contacts []ProfileInfo `json:"contacts"` 130 | Count int `json:"count"` 131 | Total int `json:"total"` 132 | Start int `json:"start"` 133 | Display int `json:"display"` 134 | } 135 | 136 | type ProfileInfo struct { 137 | DisplayName string `json:"displayName"` 138 | MID mid `json:"mid"` 139 | pictureUrl string `json:"pictureUrl"` 140 | statusMessage string `json:"statusMessage"` 141 | } 142 | 143 | func ParseRequest(r *http.Request) (*CallbackRequest, error) { 144 | result := CallbackRequest{} 145 | data, err := ioutil.ReadAll(r.Body) 146 | if err != nil { 147 | return nil, err 148 | } 149 | err = json.Unmarshal(data, &result) 150 | if err != nil { 151 | return nil, err 152 | } 153 | return &result, nil 154 | } 155 | 156 | func ParseProfileResponse(r *http.Response) (*UserProfileResponse, error) { 157 | result := UserProfileResponse{} 158 | data, err := ioutil.ReadAll(r.Body) 159 | if err != nil { 160 | return nil, err 161 | } 162 | err = json.Unmarshal(data, &result) 163 | if err != nil { 164 | return nil, err 165 | } 166 | return &result, nil 167 | 168 | } 169 | 170 | // checkSignature reports whether messageMAC is a valid HMAC tag for message. 171 | func CheckSignature(message, messageMAC, key []byte) bool { 172 | mac := hmac.New(sha256.New, key) 173 | mac.Write(message) 174 | expectedMAC := mac.Sum(nil) 175 | return hmac.Equal(messageMAC, expectedMAC) 176 | } 177 | 178 | // Set default fixed value for request 179 | func (r *Request) SetDefaults() { 180 | r.ToChannel = DefaultToChannel 181 | r.EventType = EventSendMessage 182 | } 183 | 184 | func (r *Request) AddTargetUser(m mid) error { 185 | if len(r.To) >= 150 { 186 | return ErrUserExceed 187 | } 188 | r.To = append(r.To, m) 189 | return nil 190 | } 191 | 192 | func (r *Request) SetText(text string) error { 193 | r.Content.ToType = ToTypeUser 194 | r.Content.ContentType = TextMessage 195 | r.Content.Text = text 196 | return nil 197 | } 198 | 199 | func (r *Request) SetImage(originalContentUrl, previewImageUrl string) error { 200 | r.Content.ToType = ToTypeUser 201 | r.Content.ContentType = ImageMessage 202 | r.Content.OriginalContentUrl = originalContentUrl 203 | r.Content.PreviewImageUrl = previewImageUrl 204 | return nil 205 | } 206 | 207 | func (r *Request) SetVideo(originalContentUrl, previewImageUrl string) error { 208 | r.Content.ToType = ToTypeUser 209 | r.Content.ContentType = VideoMessage 210 | r.Content.OriginalContentUrl = originalContentUrl 211 | r.Content.PreviewImageUrl = previewImageUrl 212 | return nil 213 | } 214 | 215 | func (r *Request) SetAudio(originalContentUrl, previewImageUrl string) error { 216 | r.Content.ToType = ToTypeUser 217 | r.Content.ContentType = AudioMessage 218 | r.Content.OriginalContentUrl = originalContentUrl 219 | r.Content.PreviewImageUrl = previewImageUrl 220 | return nil 221 | } 222 | 223 | func (r *Request) SetLocation(text, title string, latitude, longitude float64) error { 224 | r.Content.ToType = ToTypeUser 225 | r.Content.ContentType = LocationMessage 226 | r.Content.Text = text 227 | r.Content.Location = new(Location) 228 | r.Content.Location.Title = title 229 | r.Content.Location.Latitude = latitude 230 | r.Content.Location.Longitude = longitude 231 | return nil 232 | } 233 | 234 | func (r *Request) SetSticker(s *Sticker) error { 235 | r.Content.ToType = ToTypeUser 236 | r.Content.ContentType = StickerMessage 237 | 238 | r.Content.ContentMetadata = new(contentMetadata) 239 | r.Content.ContentMetadata.Stkid = s.Stkid 240 | r.Content.ContentMetadata.Stkpkgid = s.Stkpkgid 241 | r.Content.ContentMetadata.Stkver = s.Stkver 242 | return nil 243 | } 244 | -------------------------------------------------------------------------------- /lbot/main_test.go: -------------------------------------------------------------------------------- 1 | package lbot 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | // "io/ioutil" 7 | "log" 8 | "net/http" 9 | "net/http/httptest" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | const ( 15 | channelSecret = "00000000000000000000000000000000" 16 | ) 17 | 18 | func TestParseRequest(t *testing.T) { 19 | payload := ` 20 | {"result":[ 21 | { 22 | "from":"u2ddf2eb3c959e561f6c9fa2ea732e7eb8", 23 | "fromChannel":"1341301815", 24 | "to":["u0cc15697597f61dd8b01cea8b027050e"], 25 | "toChannel":1441301333, 26 | "eventType":"138311609000106303", 27 | "id":"ABCDEF-12345678901", 28 | "content": { 29 | "location":null, 30 | "id":"325708", 31 | "contentType":1, 32 | "from":"uff2aec188e58752ee1fb0f9507c6529a", 33 | "createdTime":1332394961610, 34 | "to":["u0a556cffd4da0dd89c94fb36e36e1cdc"], 35 | "toType":1, 36 | "contentMetadata":null, 37 | "text":"Hello, BOT API Server!" 38 | } 39 | } 40 | ]}` 41 | 42 | // payload = `null` 43 | req, _ := http.NewRequest("POST", "", strings.NewReader(string(payload))) 44 | req.Header.Add("Content-Type", "application/json;charset=UTF-8") 45 | req.Header.Add("User-Agent", "ChannelEventDispatcher/1.0") 46 | 47 | actual, err := ParseRequest(req) 48 | if err != nil { 49 | t.Error(err) 50 | t.FailNow() 51 | } 52 | if actual.Result == nil { 53 | t.Error("Result == nil") 54 | } 55 | 56 | if len(actual.Result) != 1 { 57 | t.Error("Result != 1") 58 | } 59 | 60 | if actual.Result[0].From != "u2ddf2eb3c959e561f6c9fa2ea732e7eb8" { 61 | t.Error("actual.Result[0].From != u2ddf2eb3c959e561f6c9fa2ea732e7eb8") 62 | } 63 | 64 | if actual.Result[0].Content.Location != nil { 65 | t.Error("actual.Result[0].Content.Location != nil") 66 | } 67 | 68 | if actual.Result[0].Content.Id != "325708" { 69 | t.Error("actual.Result[0].Content.Id != 325708") 70 | } 71 | 72 | if actual.Result[0].Content.ContentType != 1 { 73 | t.Errorf("actual.Result[0].Content.ContentType != %v", 1) 74 | } 75 | if actual.Result[0].Content.From != "uff2aec188e58752ee1fb0f9507c6529a" { 76 | t.Errorf("actual.Result[0].Content.From != %v", "uff2aec188e58752ee1fb0f9507c6529a") 77 | } 78 | if actual.Result[0].Content.Text != "Hello, BOT API Server!" { 79 | t.Errorf("actual.Result[0].Content.Text != %v", "Hello, BOT API Server!") 80 | } 81 | 82 | // fmt.Printf("%s\n", out) 83 | 84 | } 85 | 86 | func TestParseRequest2(t *testing.T) { 87 | payload := ` 88 | {"result":[ 89 | { 90 | "from":"u2ddf2eb3c959e561f6c9fa2ea732e7eb8", 91 | "fromChannel":1341301815, 92 | "to":["u0cc15697597f61dd8b01cea8b027050e"], 93 | "toChannel":1441301333, 94 | "eventType":"138311609000106303", 95 | "id":"ABCDEF-12345678901", 96 | "content": { 97 | "location":null, 98 | "id":"325708", 99 | "contentType":1, 100 | "from":"uff2aec188e58752ee1fb0f9507c6529a", 101 | "createdTime":1332394961610, 102 | "to":["u0a556cffd4da0dd89c94fb36e36e1cdc"], 103 | "toType":1, 104 | "contentMetadata":null, 105 | "text":"Hello, BOT API Server!" 106 | } 107 | } 108 | ]}` 109 | 110 | // payload = `null` 111 | req, _ := http.NewRequest("POST", "", strings.NewReader(string(payload))) 112 | req.Header.Add("Content-Type", "application/json;charset=UTF-8") 113 | req.Header.Add("User-Agent", "ChannelEventDispatcher/1.0") 114 | 115 | actual, err := ParseRequest(req) 116 | if err != nil { 117 | t.Error(err) 118 | t.FailNow() 119 | } 120 | if actual.Result == nil { 121 | t.Error("Result == nil") 122 | } 123 | 124 | if len(actual.Result) != 1 { 125 | t.Error("Result != 1") 126 | } 127 | 128 | if actual.Result[0].From != "u2ddf2eb3c959e561f6c9fa2ea732e7eb8" { 129 | t.Error("actual.Result[0].From != u2ddf2eb3c959e561f6c9fa2ea732e7eb8") 130 | } 131 | 132 | if actual.Result[0].Content.Location != nil { 133 | t.Error("actual.Result[0].Content.Location != nil") 134 | } 135 | 136 | if actual.Result[0].Content.Id != "325708" { 137 | t.Error("actual.Result[0].Content.Id != 325708") 138 | } 139 | 140 | if actual.Result[0].Content.ContentType != 1 { 141 | t.Errorf("actual.Result[0].Content.ContentType != %v", 1) 142 | } 143 | if actual.Result[0].Content.From != "uff2aec188e58752ee1fb0f9507c6529a" { 144 | t.Errorf("actual.Result[0].Content.From != %v", "uff2aec188e58752ee1fb0f9507c6529a") 145 | } 146 | if actual.Result[0].Content.Text != "Hello, BOT API Server!" { 147 | t.Errorf("actual.Result[0].Content.Text != %v", "Hello, BOT API Server!") 148 | } 149 | 150 | // fmt.Printf("%s\n", out) 151 | 152 | } 153 | 154 | func TestParseOperationRequest(t *testing.T) { 155 | payload := ` 156 | {"result":[ 157 | { 158 | "from":"u2ddf2eb3c959e561f6c9fa2ea732e7eb8", 159 | "fromChannel":"1341301815", 160 | "to":["u0cc15697597f61dd8b01cea8b027050e"], 161 | "toChannel":1441301333, 162 | "eventType":"138311609100106403", 163 | "id":"ABCDEF-12345678901", 164 | "content": { 165 | "params":[ 166 | "u0f3bfc598b061eba02183bfc5280886a", 167 | null, 168 | null 169 | ], 170 | "revision":2469, 171 | "opType":4 172 | } 173 | } 174 | ]}` 175 | 176 | // payload = `null` 177 | req, _ := http.NewRequest("POST", "", strings.NewReader(string(payload))) 178 | req.Header.Add("Content-Type", "application/json;charset=UTF-8") 179 | req.Header.Add("User-Agent", "ChannelEventDispatcher/1.0") 180 | 181 | actual, err := ParseRequest(req) 182 | if err != nil { 183 | t.Error(err) 184 | t.FailNow() 185 | } 186 | if actual.Result == nil { 187 | t.Error("Result == nil") 188 | } 189 | 190 | if len(actual.Result) != 1 { 191 | t.Error("Result != 1") 192 | } 193 | 194 | if actual.Result[0].From != "u2ddf2eb3c959e561f6c9fa2ea732e7eb8" { 195 | t.Error("actual.Result[0].From != u2ddf2eb3c959e561f6c9fa2ea732e7eb8") 196 | } 197 | 198 | if actual.Result[0].Content.Location != nil { 199 | t.Error("actual.Result[0].Content.Location != nil") 200 | } 201 | 202 | if actual.Result[0].Content.OpType != 4 { 203 | t.Errorf("actual.Result[0].Content.opType != %v", 4) 204 | } 205 | 206 | if *actual.Result[0].Content.Params[0] != "u0f3bfc598b061eba02183bfc5280886a" { 207 | t.Errorf("*actual.Result[0].Content.Params[0] != %v", "u0f3bfc598b061eba02183bfc5280886a") 208 | } 209 | 210 | if actual.Result[0].Content.Params[1] != nil { 211 | t.Errorf("actual.Result[0].Content.Params[1] != %v", "u0f3bfc598b061eba02183bfc5280886a") 212 | } 213 | 214 | if actual.Result[0].Content.Params[2] != nil { 215 | t.Errorf("actual.Result[0].Content.Params[2] != %v", nil) 216 | } 217 | 218 | // fmt.Printf("%s\n", out) 219 | 220 | } 221 | 222 | func TestMarshalRequest(t *testing.T) { 223 | var payload Request 224 | payload.SetDefaults() 225 | payload.SetText("おはいよ") 226 | payload.AddTargetUser("mid") 227 | 228 | out, err := json.Marshal(payload) 229 | if err != nil { 230 | t.Error(err) 231 | } 232 | var target = `{"to":["mid"],"toChannel":1383378250,"eventType":"138311608800106203","content":{"contentType":1,"toType":1,"text":"おはいよ"}}` 233 | 234 | if target != string(out) { 235 | t.Errorf("Result mismatch, expected %v, actual %v\n", target, string(out)) 236 | } 237 | } 238 | 239 | func TestParseProfile(t *testing.T) { 240 | payload := `{ 241 | "contacts": [ 242 | { 243 | "displayName": "BOT API", 244 | "mid": "u0047556f2e40dba2456887320ba7c76d", 245 | "pictureUrl": "http://dl.profile.line.naver.jp/abcdefghijklmn", 246 | "statusMessage": "Hello, LINE!" 247 | } 248 | ], 249 | "count": 1, 250 | "display": 1, 251 | "pagingRequest": { 252 | "display": 1, 253 | "sortBy": "MID", 254 | "start": 1 255 | }, 256 | "start": 1, 257 | "total": 1 258 | }` 259 | 260 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 261 | fmt.Fprintln(w, payload) 262 | })) 263 | defer ts.Close() 264 | 265 | res, err := http.Get(ts.URL) 266 | if err != nil { 267 | log.Fatal(err) 268 | } 269 | 270 | actual, err := ParseProfileResponse(res) 271 | if err != nil { 272 | log.Fatal(err) 273 | } 274 | 275 | if actual.Count != 1 { 276 | t.Errorf("actual.Count!= %v", 1) 277 | } 278 | if actual.Display != 1 { 279 | t.Errorf("actual.Display!= %v", 1) 280 | } 281 | if actual.Start != 1 { 282 | t.Errorf("actual.Start!= %v", 1) 283 | } 284 | 285 | if actual.Total != 1 { 286 | t.Errorf("actual.Total!= %v", 1) 287 | } 288 | 289 | if len(actual.Contacts) != 1 { 290 | t.Errorf("len(actual.Contacts) != %v", 1) 291 | } 292 | 293 | if actual.Contacts[0].MID != "u0047556f2e40dba2456887320ba7c76d" { 294 | t.Errorf("actual.Contacts[0].MID != %v", "u0047556f2e40dba2456887320ba7c76d") 295 | } 296 | 297 | } 298 | 299 | func TestSetText(t *testing.T) { 300 | var r Request 301 | r.SetText("text") 302 | if r.Content.ToType != ToTypeUser { 303 | t.Errorf("r.Content.ToType != %v\n", ToTypeUser) 304 | } 305 | if r.Content.ContentType != TextMessage { 306 | t.Errorf("r.Content.ContentType != %v\n", TextMessage) 307 | } 308 | 309 | if r.Content.Text != "text" { 310 | t.Errorf("r.Content.Text != %v\n", "text") 311 | } 312 | 313 | } 314 | 315 | func TestSetImage(t *testing.T) { 316 | var r Request 317 | r.SetImage("originalContentUrl", "previewImageUrl") 318 | if r.Content.ToType != ToTypeUser { 319 | t.Errorf("r.Content.ToType != %v\n", ToTypeUser) 320 | } 321 | if r.Content.ContentType != ImageMessage { 322 | t.Errorf("r.Content.ContentType != %v\n", ImageMessage) 323 | } 324 | 325 | if r.Content.OriginalContentUrl != "originalContentUrl" { 326 | t.Errorf("r.Content.OriginalContentUrl != %v\n", "originalContentUrl") 327 | } 328 | 329 | if r.Content.PreviewImageUrl != "previewImageUrl" { 330 | t.Errorf("r.Content.PreviewImageUrl != %v\n", "previewImageUrl") 331 | } 332 | 333 | } 334 | --------------------------------------------------------------------------------