四、LineBot接收及回應訊息

在Line中,使用者與聊天機器人對話時,輸入的訊息(包括文字、圖片、聲音及影片等)會傳到我們建立的網頁伺服器中,並由網頁伺服器中,webhook設定網址的方法(在我的範例是calback方法)接收此訊息,接收到訊息後,您就可針對接收的訊息進行處理,並回應給使用者。

1. 了解Line傳送給網頁伺服器的資料格式 (說明影片)

聊天機器人與網頁伺服器間傳遞的訊息為json格式的字串,您可以在Line中對聊天機器人輸入文字、圖片、貼圖等訊息,並由Eclipse的console中,查看callback方法會接收到怎樣的json格式的字串。如下在Line中輸入 Hello!

在Eclipse的console中會顯示如下json格式的字串

{
    "events": [
        {
            "type": "message",
            "replyToken": "18dd8eaf30c8404da1de06002cde05b9",
            "source": {
                "userId": "U470a93ec2caaa5ab9a42f528884ff27b",
                "type": "user"
            },
            "timestamp": 1497875299568,
            "message": {
                "type": "text",
                "id": "6263732340746",
                "text": "Hello!"
            }
        }
    ]
}

練習:您可以在Line中輸入不同的訊息,如圖片、錄音、貼圖、位置等,並Eclipse的console中觀看獲得的json格式的字串。

您也可到Line Messaging API Reference查看完整的json資料格式,如下:


2.  webhook event object的說明

webhook接收的json格式字串是一個webhook event object,此物件(events)包含1~多個event物件。

每個event物件包含以下幾個屬性:


3. 將json格式的字串轉換為物件 (說明影片)

取得此json格式字串的某些值,若用字串處理會非常麻煩(如replytoken的值),我們通常會將此字串轉換為物件再進行後續處理,而目前已有很多API能把json格式的字串轉換為物件,如java的jackson API、C#的Newtonsoft.Json

要將json格式的字串轉換為物件,需先製作json格式的字串相對應的物件,在此須製作Source、Message、Event、EventWrapper物件,完整程式碼如下:

public class Source {
    private String type;
    private String userId;
    private String groupId;
    private String roomId;
    public String getUserId() {
        return userId;
    }
    public void setUserId(String userId) {
        this.userId = userId;
    }
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    public String getGroupId() {
        return groupId;
    }
    public void setGroupId(String groupId) {
        this.groupId = groupId;
    }
    public String getRoomId() {
        return roomId;
    }
    public void setRoomId(String roomId) {
        this.roomId = roomId;
    }    
}

public class Message {
    private String id;
    private String type;
    private String text;
    private String filename;
    private String filesize;
    private String title;
    private String address;
    private String latitude;
    private String longitude;
    private String packageId;
    private String stickerId;
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public String getLatitude() {
        return latitude;
    }
    public void setLatitude(String latitude) {
        this.latitude = latitude;
    }
    public String getLongitude() {
        return longitude;
    }
    public void setLongitude(String longitude) {
        this.longitude = longitude;
    }
    public String getPackageId() {
        return packageId;
    }
    public void setPackageId(String packageId) {
        this.packageId = packageId;
    }
    public String getStickerId() {
        return stickerId;
    }
    public void setStickerId(String stickerId) {
        this.stickerId = stickerId;
    }
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    public String getFilename() {
        return filename;
    }
    public void setFilename(String filename) {
        this.filename = filename;
    }
    public String getFilesize() {
        return filesize;
    }
    public void setFilesize(String filesize) {
        this.filesize = filesize;
    }
}

public class Event {
    private String replyToken;
    private String type;
    private Source source;
    private String timestamp;
    private Message message;
    public String getReplyToken() {
        return replyToken;
    }
    public void setReplyToken(String replyToken) {
        this.replyToken = replyToken;
    }
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    public Source getSource() {
        return source;
    }
    public void setSource(Source source) {
        this.source = source;
    }
    public String getTimestamp() {
        return timestamp;
    }
    public void setTimestamp(String timestamp) {
        this.timestamp = timestamp;
    }
    public Message getMessage() {
        return message;
    }
    public void setMessage(Message message) {
        this.message = message;
    }
}

public class EventWrapper {
    private List<Event> events;
    public List<Event> getEvents() {
        return events;
    }
    public void setEvents(List<Event> events) {
        this.events = events;
    }    
}

在設計這些物件後,callback方法的參數要修改,因spring mvc的controller會自動將傳進來的json格式字串自動轉換為物件,所以只需將程式修改如下:

@RestController
public class LineBotRSController {
    @RequestMapping(value="/callback")
    public void callback(@RequestBody EventWrapper events) {  
        for(Event event:events.getEvents()){ //用一個迴圈,一個個取得event物件
            System.out.println(event.getReplyToken()); //透過Event物件的getReplyToken()方法取得replyToken,並在console顯示此replyToken
        }
    }
}


4. 回應訊息(reply message)(說明影片)

回應訊息可參考Line Messaging API的Reply mesage說明

HTTP request
POST https://api.line.me/v2/bot/message/reply

Request headers
Content-Type    application/json
Authorization    Bearer {Channel Access Token}

Request body
replyToken    String
messages    Array of send message objects (Max: 5)

如上說明,回應訊息是透過post方法,將回應訊息放在request body,傳送到https://api.line.me/v2/bot/message/reply網址,傳送時Request headers需有Content-Type及Authorization

有幾點須注意:

a. Channel Access Token可在設定webhook的網頁獲得,

b. 回傳的訊息須放在request body,是一個json格式的字串,包含replyToken及messages兩個屬性

c. replyToken會在30秒後失效,所以您需在接收到訊息時立刻回應

d. 每次回傳的訊息最多5個

因此要回傳訊息我們增加了一個sendResponseMessages方法及修改callback方法,程式碼如下,此程式會將使用者傳來的文字訊息在其前加上echo字串後,回應給使用者。

@RestController
public class LineBotRSController {
    private String accessToken="k210L48XnXW2xONK9WsQjl+fcEUmXswIyLQ1fpIZebkupa0ET+IVphTM5dknlWTW65aCf9WkR+vx/nFLa+ZW1b5FTy22xhReEOZpf6TYF9nk9I0wU/gT6jlCwdrvG9ySugBC1LSo4uWVaY7C9ig6HQdB04t89/1O/w1cDnyilFU=";
    
    @RequestMapping(value="/callback")
    public void callback(@RequestBody EventWrapper events) {  
        for(Event event:events.getEvents()){
            switch(event.getType()){
               case "message": //當event為message時進入此case執行,其他event(如follow、unfollow、leave等)的case在此省略,您可自己增加           
                   System.out.print("This is a message event!");
                   switch(event.getMessage().getType()){
                   case "text": //當message type為text時,進入此case執行,目前子是將使用者傳來的文字訊息在其前加上echo字串後,回傳給使用者
                       sendResponseMessages(event.getReplyToken(), event.getMessage().getText());
                       System.out.println("This is a text message. It's replytoken is "+event.getReplyToken());
                       break;
                   case "image"://當message type為image時,進入此case執行,
                       System.out.println("This is a image message. It's replytoken is "+event.getReplyToken());
                       break;
                   case "audio"://當message type為audio時,進入此case執行,
                       System.out.println("This is a audio message. It's replytoken is "+event.getReplyToken());
                       break;
                   case "video"://當message type為video時,進入此case執行,
                       System.out.println("This is a video message. It's replytoken is "+event.getReplyToken());
                       break;
                   case "file"://當message type為file時,進入此case執行,
                       System.out.println("This is a file message. It's replytoken is "+event.getReplyToken());
                       break;
                   case "sticker"://當message type為sticker時,進入此case執行,
                       System.out.println("This is a sticker message. It's replytoken is "+event.getReplyToken());
                       break;
                   case "location"://當message type為location時,進入此case執行,
                       System.out.println("This is a location message. It's replytoken is "+event.getReplyToken());
                       break;
                   }
                   
                   break;
               }
        }
    }
    
    private void sendResponseMessages(String replyToken, String message) {
        try {
            message = "{\"replyToken\":\""+replyToken+"\",\"messages\":[{\"type\":\"text\",\"text\":\"echo "+message+"\"}]}"; //回傳的json格式訊息
            URL myurl = new URL("https://api.line.me/v2/bot/message/reply"); //回傳的網址
            HttpsURLConnection con = (HttpsURLConnection)myurl.openConnection(); //使用HttpsURLConnection建立https連線
            con.setRequestMethod("POST");//設定post方法
            con.setRequestProperty("Content-Type", "application/json; charset=utf-8"); //設定Content-Type為json
            con.setRequestProperty("Authorization", "Bearer "+this.accessToken); //設定Authorization
            con.setDoOutput(true);
            con.setDoInput(true);
            DataOutputStream output = new DataOutputStream( con.getOutputStream() ); //開啟HttpsURLConnection的連線
            output.write( message.getBytes(Charset.forName("utf-8")) );  //回傳訊息編碼為utf-8
            output.close();
            System.out.println("Resp Code:"+con .getResponseCode()+"; Resp Message:"+con .getResponseMessage()); //顯示回傳的結果,若code為200代表回傳成功
        } catch (MalformedURLException e) {
            System.out.println("Message: "+e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("Message: "+e.getMessage());
            e.printStackTrace();
        }
    }
}