github.com/line/line-bot-sdk-go/v7@v7.21.0/examples/kitchensink/server.go (about) 1 // Copyright 2016 LINE Corporation 2 // 3 // LINE Corporation licenses this file to you under the Apache License, 4 // version 2.0 (the "License"); you may not use this file except in compliance 5 // with the License. You may obtain a copy of the License at: 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations 13 // under the License. 14 15 package main 16 17 import ( 18 "fmt" 19 "io" 20 "log" 21 "net/http" 22 "os" 23 "os/exec" 24 "path/filepath" 25 26 "github.com/line/line-bot-sdk-go/v7/linebot" 27 ) 28 29 func main() { 30 app, err := NewKitchenSink( 31 os.Getenv("CHANNEL_SECRET"), 32 os.Getenv("CHANNEL_TOKEN"), 33 os.Getenv("APP_BASE_URL"), 34 ) 35 if err != nil { 36 log.Fatal(err) 37 } 38 39 // serve /static/** files 40 staticFileServer := http.FileServer(http.Dir("static")) 41 http.HandleFunc("/static/", http.StripPrefix("/static/", staticFileServer).ServeHTTP) 42 // serve /downloaded/** files 43 downloadedFileServer := http.FileServer(http.Dir(app.downloadDir)) 44 http.HandleFunc("/downloaded/", http.StripPrefix("/downloaded/", downloadedFileServer).ServeHTTP) 45 46 http.HandleFunc("/callback", app.Callback) 47 // This is just a sample code. 48 // For actually use, you must support HTTPS by using `ListenAndServeTLS`, reverse proxy or etc. 49 if err := http.ListenAndServe(":"+os.Getenv("PORT"), nil); err != nil { 50 log.Fatal(err) 51 } 52 } 53 54 // KitchenSink app 55 type KitchenSink struct { 56 bot *linebot.Client 57 appBaseURL string 58 downloadDir string 59 } 60 61 // NewKitchenSink function 62 func NewKitchenSink(channelSecret, channelToken, appBaseURL string) (*KitchenSink, error) { 63 apiEndpointBase := os.Getenv("ENDPOINT_BASE") 64 if apiEndpointBase == "" { 65 apiEndpointBase = linebot.APIEndpointBase 66 } 67 bot, err := linebot.New( 68 channelSecret, 69 channelToken, 70 linebot.WithEndpointBase(apiEndpointBase), // Usually you omit this. 71 ) 72 if err != nil { 73 return nil, err 74 } 75 downloadDir := filepath.Join(filepath.Dir(os.Args[0]), "line-bot") 76 _, err = os.Stat(downloadDir) 77 if err != nil { 78 if err := os.Mkdir(downloadDir, 0777); err != nil { 79 return nil, err 80 } 81 } 82 return &KitchenSink{ 83 bot: bot, 84 appBaseURL: appBaseURL, 85 downloadDir: downloadDir, 86 }, nil 87 } 88 89 // Callback function for http server 90 func (app *KitchenSink) Callback(w http.ResponseWriter, r *http.Request) { 91 events, err := app.bot.ParseRequest(r) 92 if err != nil { 93 if err == linebot.ErrInvalidSignature { 94 w.WriteHeader(400) 95 } else { 96 w.WriteHeader(500) 97 } 98 return 99 } 100 for _, event := range events { 101 log.Printf("Got event %v", event) 102 switch event.Type { 103 case linebot.EventTypeMessage: 104 switch message := event.Message.(type) { 105 case *linebot.TextMessage: 106 if err := app.handleText(message, event.ReplyToken, event.Source); err != nil { 107 log.Print(err) 108 } 109 case *linebot.ImageMessage: 110 if err := app.handleImage(message, event.ReplyToken); err != nil { 111 log.Print(err) 112 } 113 case *linebot.VideoMessage: 114 if err := app.handleVideo(message, event.ReplyToken); err != nil { 115 log.Print(err) 116 } 117 case *linebot.AudioMessage: 118 if err := app.handleAudio(message, event.ReplyToken); err != nil { 119 log.Print(err) 120 } 121 case *linebot.FileMessage: 122 if err := app.handleFile(message, event.ReplyToken); err != nil { 123 log.Print(err) 124 } 125 case *linebot.LocationMessage: 126 if err := app.handleLocation(message, event.ReplyToken); err != nil { 127 log.Print(err) 128 } 129 case *linebot.StickerMessage: 130 if err := app.handleSticker(message, event.ReplyToken); err != nil { 131 log.Print(err) 132 } 133 default: 134 log.Printf("Unknown message: %v", message) 135 } 136 case linebot.EventTypeFollow: 137 if err := app.replyText(event.ReplyToken, "Got followed event"); err != nil { 138 log.Print(err) 139 } 140 case linebot.EventTypeUnfollow: 141 log.Printf("Unfollowed this bot: %v", event) 142 case linebot.EventTypeJoin: 143 if err := app.replyText(event.ReplyToken, "Joined "+string(event.Source.Type)); err != nil { 144 log.Print(err) 145 } 146 case linebot.EventTypeLeave: 147 log.Printf("Left: %v", event) 148 case linebot.EventTypePostback: 149 data := event.Postback.Data 150 if data == "DATE" || data == "TIME" || data == "DATETIME" { 151 data += fmt.Sprintf("(%v)", *event.Postback.Params) 152 } 153 if err := app.replyText(event.ReplyToken, "Got postback: "+data); err != nil { 154 log.Print(err) 155 } 156 case linebot.EventTypeBeacon: 157 if err := app.replyText(event.ReplyToken, "Got beacon: "+event.Beacon.Hwid); err != nil { 158 log.Print(err) 159 } 160 default: 161 log.Printf("Unknown event: %v", event) 162 } 163 } 164 } 165 166 func (app *KitchenSink) handleText(message *linebot.TextMessage, replyToken string, source *linebot.EventSource) error { 167 switch message.Text { 168 case "profile": 169 if source.UserID != "" { 170 profile, err := app.bot.GetProfile(source.UserID).Do() 171 if err != nil { 172 return app.replyText(replyToken, err.Error()) 173 } 174 if _, err := app.bot.ReplyMessage( 175 replyToken, 176 linebot.NewTextMessage("Display name: "+profile.DisplayName), 177 linebot.NewTextMessage("Status message: "+profile.StatusMessage), 178 ).Do(); err != nil { 179 return err 180 } 181 } else { 182 return app.replyText(replyToken, "Bot can't use profile API without user ID") 183 } 184 case "buttons": 185 imageURL := app.appBaseURL + "/static/buttons/1040.jpg" 186 template := linebot.NewButtonsTemplate( 187 imageURL, "My button sample", "Hello, my button", 188 linebot.NewURIAction("Go to line.me", "https://line.me"), 189 linebot.NewPostbackAction("Say hello1", "hello こんにちは", "", "hello こんにちは", "", ""), 190 linebot.NewPostbackAction("言 hello2", "hello こんにちは", "hello こんにちは", "", "", ""), 191 linebot.NewMessageAction("Say message", "Rice=米"), 192 ) 193 if _, err := app.bot.ReplyMessage( 194 replyToken, 195 linebot.NewTemplateMessage("Buttons alt text", template), 196 ).Do(); err != nil { 197 return err 198 } 199 case "confirm": 200 template := linebot.NewConfirmTemplate( 201 "Do it?", 202 linebot.NewMessageAction("Yes", "Yes!"), 203 linebot.NewMessageAction("No", "No!"), 204 ) 205 if _, err := app.bot.ReplyMessage( 206 replyToken, 207 linebot.NewTemplateMessage("Confirm alt text", template), 208 ).Do(); err != nil { 209 return err 210 } 211 case "carousel": 212 imageURL := app.appBaseURL + "/static/buttons/1040.jpg" 213 template := linebot.NewCarouselTemplate( 214 linebot.NewCarouselColumn( 215 imageURL, "hoge", "fuga", 216 linebot.NewURIAction("Go to line.me", "https://line.me"), 217 linebot.NewPostbackAction("Say hello1", "hello こんにちは", "", "", "", ""), 218 ), 219 linebot.NewCarouselColumn( 220 imageURL, "hoge", "fuga", 221 linebot.NewPostbackAction("言 hello2", "hello こんにちは", "hello こんにちは", "", "", ""), 222 linebot.NewMessageAction("Say message", "Rice=米"), 223 ), 224 ) 225 if _, err := app.bot.ReplyMessage( 226 replyToken, 227 linebot.NewTemplateMessage("Carousel alt text", template), 228 ).Do(); err != nil { 229 return err 230 } 231 case "image carousel": 232 imageURL := app.appBaseURL + "/static/buttons/1040.jpg" 233 template := linebot.NewImageCarouselTemplate( 234 linebot.NewImageCarouselColumn( 235 imageURL, 236 linebot.NewURIAction("Go to LINE", "https://line.me"), 237 ), 238 linebot.NewImageCarouselColumn( 239 imageURL, 240 linebot.NewPostbackAction("Say hello1", "hello こんにちは", "", "", "", ""), 241 ), 242 linebot.NewImageCarouselColumn( 243 imageURL, 244 linebot.NewMessageAction("Say message", "Rice=米"), 245 ), 246 linebot.NewImageCarouselColumn( 247 imageURL, 248 linebot.NewDatetimePickerAction("datetime", "DATETIME", "datetime", "", "", ""), 249 ), 250 ) 251 if _, err := app.bot.ReplyMessage( 252 replyToken, 253 linebot.NewTemplateMessage("Image carousel alt text", template), 254 ).Do(); err != nil { 255 return err 256 } 257 case "datetime": 258 template := linebot.NewButtonsTemplate( 259 "", "", "Select date / time !", 260 linebot.NewDatetimePickerAction("date", "DATE", "date", "", "", ""), 261 linebot.NewDatetimePickerAction("time", "TIME", "time", "", "", ""), 262 linebot.NewDatetimePickerAction("datetime", "DATETIME", "datetime", "", "", ""), 263 ) 264 if _, err := app.bot.ReplyMessage( 265 replyToken, 266 linebot.NewTemplateMessage("Datetime pickers alt text", template), 267 ).Do(); err != nil { 268 return err 269 } 270 case "flex": 271 // { 272 // "type": "bubble", 273 // "body": { 274 // "type": "box", 275 // "layout": "horizontal", 276 // "contents": [ 277 // { 278 // "type": "text", 279 // "text": "Hello," 280 // }, 281 // { 282 // "type": "text", 283 // "text": "World!" 284 // } 285 // ] 286 // } 287 // } 288 contents := &linebot.BubbleContainer{ 289 Type: linebot.FlexContainerTypeBubble, 290 Body: &linebot.BoxComponent{ 291 Type: linebot.FlexComponentTypeBox, 292 Layout: linebot.FlexBoxLayoutTypeHorizontal, 293 Contents: []linebot.FlexComponent{ 294 &linebot.TextComponent{ 295 Type: linebot.FlexComponentTypeText, 296 Text: "Hello,", 297 }, 298 &linebot.TextComponent{ 299 Type: linebot.FlexComponentTypeText, 300 Text: "World!", 301 }, 302 }, 303 }, 304 } 305 if _, err := app.bot.ReplyMessage( 306 replyToken, 307 linebot.NewFlexMessage("Flex message alt text", contents), 308 ).Do(); err != nil { 309 return err 310 } 311 case "flex carousel": 312 // { 313 // "type": "carousel", 314 // "contents": [ 315 // { 316 // "type": "bubble", 317 // "body": { 318 // "type": "box", 319 // "layout": "vertical", 320 // "contents": [ 321 // { 322 // "type": "text", 323 // "text": "First bubble" 324 // } 325 // ] 326 // } 327 // }, 328 // { 329 // "type": "bubble", 330 // "body": { 331 // "type": "box", 332 // "layout": "vertical", 333 // "contents": [ 334 // { 335 // "type": "text", 336 // "text": "Second bubble" 337 // } 338 // ] 339 // } 340 // } 341 // ] 342 // } 343 contents := &linebot.CarouselContainer{ 344 Type: linebot.FlexContainerTypeCarousel, 345 Contents: []*linebot.BubbleContainer{ 346 { 347 Type: linebot.FlexContainerTypeBubble, 348 Body: &linebot.BoxComponent{ 349 Type: linebot.FlexComponentTypeBox, 350 Layout: linebot.FlexBoxLayoutTypeVertical, 351 Contents: []linebot.FlexComponent{ 352 &linebot.TextComponent{ 353 Type: linebot.FlexComponentTypeText, 354 Text: "First bubble", 355 }, 356 }, 357 }, 358 }, 359 { 360 Type: linebot.FlexContainerTypeBubble, 361 Body: &linebot.BoxComponent{ 362 Type: linebot.FlexComponentTypeBox, 363 Layout: linebot.FlexBoxLayoutTypeVertical, 364 Contents: []linebot.FlexComponent{ 365 &linebot.TextComponent{ 366 Type: linebot.FlexComponentTypeText, 367 Text: "Second bubble", 368 }, 369 }, 370 }, 371 }, 372 }, 373 } 374 if _, err := app.bot.ReplyMessage( 375 replyToken, 376 linebot.NewFlexMessage("Flex message alt text", contents), 377 ).Do(); err != nil { 378 return err 379 } 380 case "flex json": 381 jsonString := `{ 382 "type": "bubble", 383 "hero": { 384 "type": "image", 385 "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/01_1_cafe.png", 386 "size": "full", 387 "aspectRatio": "20:13", 388 "aspectMode": "cover", 389 "action": { 390 "type": "uri", 391 "uri": "http://linecorp.com/" 392 } 393 }, 394 "body": { 395 "type": "box", 396 "layout": "vertical", 397 "contents": [ 398 { 399 "type": "text", 400 "text": "Brown Cafe", 401 "weight": "bold", 402 "size": "xl" 403 }, 404 { 405 "type": "box", 406 "layout": "baseline", 407 "margin": "md", 408 "contents": [ 409 { 410 "type": "icon", 411 "size": "sm", 412 "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 413 }, 414 { 415 "type": "icon", 416 "size": "sm", 417 "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 418 }, 419 { 420 "type": "icon", 421 "size": "sm", 422 "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 423 }, 424 { 425 "type": "icon", 426 "size": "sm", 427 "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 428 }, 429 { 430 "type": "icon", 431 "size": "sm", 432 "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gray_star_28.png" 433 }, 434 { 435 "type": "text", 436 "text": "4.0", 437 "size": "sm", 438 "color": "#999999", 439 "margin": "md", 440 "flex": 0 441 } 442 ] 443 }, 444 { 445 "type": "box", 446 "layout": "vertical", 447 "margin": "lg", 448 "spacing": "sm", 449 "contents": [ 450 { 451 "type": "box", 452 "layout": "baseline", 453 "spacing": "sm", 454 "contents": [ 455 { 456 "type": "text", 457 "text": "Place", 458 "color": "#aaaaaa", 459 "size": "sm", 460 "flex": 1 461 }, 462 { 463 "type": "text", 464 "text": "Miraina Tower, 4-1-6 Shinjuku, Tokyo", 465 "wrap": true, 466 "color": "#666666", 467 "size": "sm", 468 "flex": 5 469 } 470 ] 471 }, 472 { 473 "type": "box", 474 "layout": "baseline", 475 "spacing": "sm", 476 "contents": [ 477 { 478 "type": "text", 479 "text": "Time", 480 "color": "#aaaaaa", 481 "size": "sm", 482 "flex": 1 483 }, 484 { 485 "type": "text", 486 "text": "10:00 - 23:00", 487 "wrap": true, 488 "color": "#666666", 489 "size": "sm", 490 "flex": 5 491 } 492 ] 493 } 494 ] 495 } 496 ] 497 }, 498 "footer": { 499 "type": "box", 500 "layout": "vertical", 501 "spacing": "sm", 502 "contents": [ 503 { 504 "type": "button", 505 "style": "link", 506 "height": "sm", 507 "action": { 508 "type": "uri", 509 "label": "CALL", 510 "uri": "https://linecorp.com" 511 } 512 }, 513 { 514 "type": "button", 515 "style": "link", 516 "height": "sm", 517 "action": { 518 "type": "uri", 519 "label": "WEBSITE", 520 "uri": "https://linecorp.com", 521 "altUri": { 522 "desktop": "https://line.me/ja/download" 523 } 524 } 525 }, 526 { 527 "type": "spacer", 528 "size": "sm" 529 } 530 ], 531 "flex": 0 532 } 533 }` 534 contents, err := linebot.UnmarshalFlexMessageJSON([]byte(jsonString)) 535 if err != nil { 536 return err 537 } 538 if _, err := app.bot.ReplyMessage( 539 replyToken, 540 linebot.NewFlexMessage("Flex message alt text", contents), 541 ).Do(); err != nil { 542 return err 543 } 544 case "imagemap": 545 if _, err := app.bot.ReplyMessage( 546 replyToken, 547 linebot.NewImagemapMessage( 548 app.appBaseURL+"/static/rich", 549 "Imagemap alt text", 550 linebot.ImagemapBaseSize{Width: 1040, Height: 1040}, 551 linebot.NewURIImagemapAction("LINE Store Manga", "https://store.line.me/family/manga/en", linebot.ImagemapArea{X: 0, Y: 0, Width: 520, Height: 520}), 552 linebot.NewURIImagemapAction("LINE Store Music", "https://store.line.me/family/music/en", linebot.ImagemapArea{X: 520, Y: 0, Width: 520, Height: 520}), 553 linebot.NewURIImagemapAction("LINE Store Play", "https://store.line.me/family/play/en", linebot.ImagemapArea{X: 0, Y: 520, Width: 520, Height: 520}), 554 linebot.NewMessageImagemapAction("URANAI!", "URANAI!", linebot.ImagemapArea{X: 520, Y: 520, Width: 520, Height: 520}), 555 ), 556 ).Do(); err != nil { 557 return err 558 } 559 case "imagemap video": 560 if _, err := app.bot.ReplyMessage( 561 replyToken, 562 linebot.NewImagemapMessage( 563 app.appBaseURL+"/static/rich", 564 "Imagemap with video alt text", 565 linebot.ImagemapBaseSize{Width: 1040, Height: 1040}, 566 linebot.NewURIImagemapAction("LINE Store Manga", "https://store.line.me/family/manga/en", linebot.ImagemapArea{X: 0, Y: 0, Width: 520, Height: 520}), 567 linebot.NewURIImagemapAction("LINE Store Music", "https://store.line.me/family/music/en", linebot.ImagemapArea{X: 520, Y: 0, Width: 520, Height: 520}), 568 linebot.NewURIImagemapAction("LINE Store Play", "https://store.line.me/family/play/en", linebot.ImagemapArea{X: 0, Y: 520, Width: 520, Height: 520}), 569 linebot.NewMessageImagemapAction("URANAI!", "URANAI!", linebot.ImagemapArea{X: 520, Y: 520, Width: 520, Height: 520}), 570 ).WithVideo(&linebot.ImagemapVideo{ 571 OriginalContentURL: app.appBaseURL + "/static/imagemap/video.mp4", 572 PreviewImageURL: app.appBaseURL + "/static/imagemap/preview.jpg", 573 Area: linebot.ImagemapArea{X: 280, Y: 385, Width: 480, Height: 270}, 574 ExternalLink: &linebot.ImagemapVideoExternalLink{LinkURI: "https://line.me", Label: "LINE"}, 575 }), 576 ).Do(); err != nil { 577 return err 578 } 579 case "quick": 580 if _, err := app.bot.ReplyMessage( 581 replyToken, 582 linebot.NewTextMessage("Select your favorite food category or send me your location!"). 583 WithQuickReplies(linebot.NewQuickReplyItems( 584 linebot.NewQuickReplyButton( 585 app.appBaseURL+"/static/quick/sushi.png", 586 linebot.NewMessageAction("Sushi", "Sushi")), 587 linebot.NewQuickReplyButton( 588 app.appBaseURL+"/static/quick/tempura.png", 589 linebot.NewMessageAction("Tempura", "Tempura")), 590 linebot.NewQuickReplyButton( 591 "", 592 linebot.NewLocationAction("Send location")), 593 linebot.NewQuickReplyButton( 594 "", 595 linebot.NewURIAction("LINE Developer", "https://developers.line.biz/")), 596 )), 597 ).Do(); err != nil { 598 return err 599 } 600 case "bye": 601 switch source.Type { 602 case linebot.EventSourceTypeUser: 603 return app.replyText(replyToken, "Bot can't leave from 1:1 chat") 604 case linebot.EventSourceTypeGroup: 605 if err := app.replyText(replyToken, "Leaving group"); err != nil { 606 return err 607 } 608 if _, err := app.bot.LeaveGroup(source.GroupID).Do(); err != nil { 609 return app.replyText(replyToken, err.Error()) 610 } 611 case linebot.EventSourceTypeRoom: 612 if err := app.replyText(replyToken, "Leaving room"); err != nil { 613 return err 614 } 615 if _, err := app.bot.LeaveRoom(source.RoomID).Do(); err != nil { 616 return app.replyText(replyToken, err.Error()) 617 } 618 } 619 default: 620 log.Printf("Echo message to %s: %s", replyToken, message.Text) 621 if _, err := app.bot.ReplyMessage( 622 replyToken, 623 linebot.NewTextMessage(message.Text), 624 ).Do(); err != nil { 625 return err 626 } 627 } 628 return nil 629 } 630 631 func (app *KitchenSink) handleImage(message *linebot.ImageMessage, replyToken string) error { 632 return app.handleHeavyContent(message.ID, func(originalContent *os.File) error { 633 // You need to install ImageMagick. 634 // And you should consider about security and scalability. 635 previewImagePath := originalContent.Name() + "-preview" 636 _, err := exec.Command("convert", "-resize", "240x", "jpeg:"+originalContent.Name(), "jpeg:"+previewImagePath).Output() 637 if err != nil { 638 return err 639 } 640 641 originalContentURL := app.appBaseURL + "/downloaded/" + filepath.Base(originalContent.Name()) 642 previewImageURL := app.appBaseURL + "/downloaded/" + filepath.Base(previewImagePath) 643 if _, err := app.bot.ReplyMessage( 644 replyToken, 645 linebot.NewImageMessage(originalContentURL, previewImageURL), 646 ).Do(); err != nil { 647 return err 648 } 649 return nil 650 }) 651 } 652 653 func (app *KitchenSink) handleVideo(message *linebot.VideoMessage, replyToken string) error { 654 return app.handleHeavyContent(message.ID, func(originalContent *os.File) error { 655 // You need to install FFmpeg and ImageMagick. 656 // And you should consider about security and scalability. 657 previewImagePath := originalContent.Name() + "-preview" 658 _, err := exec.Command("convert", "mp4:"+originalContent.Name()+"[0]", "jpeg:"+previewImagePath).Output() 659 if err != nil { 660 return err 661 } 662 663 originalContentURL := app.appBaseURL + "/downloaded/" + filepath.Base(originalContent.Name()) 664 previewImageURL := app.appBaseURL + "/downloaded/" + filepath.Base(previewImagePath) 665 if _, err := app.bot.ReplyMessage( 666 replyToken, 667 linebot.NewVideoMessage(originalContentURL, previewImageURL), 668 ).Do(); err != nil { 669 return err 670 } 671 return nil 672 }) 673 } 674 675 func (app *KitchenSink) handleAudio(message *linebot.AudioMessage, replyToken string) error { 676 return app.handleHeavyContent(message.ID, func(originalContent *os.File) error { 677 originalContentURL := app.appBaseURL + "/downloaded/" + filepath.Base(originalContent.Name()) 678 if _, err := app.bot.ReplyMessage( 679 replyToken, 680 linebot.NewAudioMessage(originalContentURL, 100), 681 ).Do(); err != nil { 682 return err 683 } 684 return nil 685 }) 686 } 687 688 func (app *KitchenSink) handleFile(message *linebot.FileMessage, replyToken string) error { 689 return app.replyText(replyToken, fmt.Sprintf("File `%s` (%d bytes) received.", message.FileName, message.FileSize)) 690 } 691 692 func (app *KitchenSink) handleLocation(message *linebot.LocationMessage, replyToken string) error { 693 if _, err := app.bot.ReplyMessage( 694 replyToken, 695 linebot.NewLocationMessage(message.Title, message.Address, message.Latitude, message.Longitude), 696 ).Do(); err != nil { 697 return err 698 } 699 return nil 700 } 701 702 func (app *KitchenSink) handleSticker(message *linebot.StickerMessage, replyToken string) error { 703 if _, err := app.bot.ReplyMessage( 704 replyToken, 705 linebot.NewStickerMessage(message.PackageID, message.StickerID), 706 ).Do(); err != nil { 707 return err 708 } 709 return nil 710 } 711 712 func (app *KitchenSink) replyText(replyToken, text string) error { 713 if _, err := app.bot.ReplyMessage( 714 replyToken, 715 linebot.NewTextMessage(text), 716 ).Do(); err != nil { 717 return err 718 } 719 return nil 720 } 721 722 func (app *KitchenSink) handleHeavyContent(messageID string, callback func(*os.File) error) error { 723 content, err := app.bot.GetMessageContent(messageID).Do() 724 if err != nil { 725 return err 726 } 727 defer content.Content.Close() 728 log.Printf("Got file: %s", content.ContentType) 729 originalContent, err := app.saveContent(content.Content) 730 if err != nil { 731 return err 732 } 733 return callback(originalContent) 734 } 735 736 func (app *KitchenSink) saveContent(content io.ReadCloser) (*os.File, error) { 737 file, err := os.CreateTemp(app.downloadDir, "") 738 if err != nil { 739 return nil, err 740 } 741 defer file.Close() 742 743 _, err = io.Copy(file, content) 744 if err != nil { 745 return nil, err 746 } 747 log.Printf("Saved %s", file.Name()) 748 return file, nil 749 }