2020年1月14日 星期二

LINE webhook整合在同一Flask

這篇介紹把LINE的webhook整合到同一個Flask裡,方便管理。但流量大的時候,還是要分開。

程式LINE webhook framework的Python版,故其它語言就沒有參考價值。下列列出模組化的部份,不列出完整程式。


首先LINE_bot物件變成兩組

#R
line_bot_api = LineBotApi('KEY')
handler = WebhookHandler('SECRET')

#E
line_bot_api_e = LineBotApi('KEY')
handler_e = WebhookHandler('SECRET')

路由分成兩個,裡面就可以做各自的事情。

'''
R
'''
@app.route("/webhook", methods=['POST'])
def callback():

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):



'''
E
'''
@app.route("/webhook02", methods=['POST'])
def callback_E():

@handler_e.add(MessageEvent, message=TextMessage)
def handle_message_E(event):


如此Restful介面就變成如下,port號一樣,僅路由不同:

https://url:port/webhook
https://url:port/webhook02




LINE Front-end Framework(LIFF)

LIFF是LINE利用web page來實現LINE原有功能無法實現的一個管道。



下面範例是開啟一個網頁,可以在網頁中抓取LINE使用者的ID及相關資料。
------------------------------------------------------------------------------------------------------------------------------------------------



我用的範例網頁程式是LINE提供,網址如下:

https://github.com/line/line-liff-starter



將這個程式放在你的網站上的一個目錄,記下該目錄的URL,下面步驟要用到。





---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

進入LIFF頁籤,點ADD新增。



---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Size是選這個LIFF窗格的佔螢幕範圍,Full是全滿,Compact是半滿。

Endpoint URL存取你的網頁的URL。

BLE feature可以直接略過,它是Bluetooth Low Energy實體物件在用的(要另外買這個東西)。





---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

結果會秀出LIFF URL,把這個URL貼到要觸發這個LIFF的地方,例如LINE的選單(RichMenu)





----------------------------------------------------------------------------------------------------------------------------------------

把LIFF URL貼到選單(RichMenu)



-------------------------------------------------------------------------------------

觸發按鈕會出現在選單上


--------------------------------------------------------------------------------------------------------------------------------





MariaDB的Pool connection

因為發現我們的JAVA程式連MySQL資料庫的建立連線過程浪費很多時間,故想用connection pool的觀念來作,找到mariaDB(MySQL的免費後繼版)有直接提供pool的功能給JAVA,故我就研究如何套用到程式上。

mariaDB原生的pool功能,測試後效能比沒用pool快4倍。要用mariaDB原生的Driver,要用JAVA 8以上的版本,資料庫軟體可直接去mariaDB的網站下載。

程式碼在最下面,程式中最後一個區塊是最佳pool範例程式,改程式很簡單。



資料庫連結的JAR檔可在下列原廠下載

https://mariadb.com/kb/en/library/mariadb-connector-j/



這裡有教Eclipse怎麼載入connector的JAR檔

https://www.jianshu.com/p/489e4f0c1621





import java.sql.*;

import org.mariadb.jdbc.MariaDbPoolDataSource;



public class helloworld {



    public static void main(String[] args) throws Exception {

        // TODO Auto-generated method stub

        long time1, time2, time3;

        Class.forName("org.mariadb.jdbc.Driver");

     

//**********************************

// Original Database connection method

//********************************** 

        time1 = System.currentTimeMillis();

        for(int i = 1; i <=1000;i++) {

            try(

 

                Connection conn = DriverManager.getConnection(

                    "jdbc:mysql://localhost:3306/drink?user=root&password=0000");

                Statement stmt = conn.createStatement();

                ResultSet rs = stmt.executeQuery("SELECT * FROM history LIMIT 6,5"))

            {

 

                rs.next();

                //System.out.println(i +  "\t" + rs.getString(2) );

            }

        }

        time2 = System.currentTimeMillis();

        time3 = time2-time1;

        System.out.println("花了:" + time3 + " ms");

     

//**********************************

// mariaDB pool with minimal modified

//**********************************     

        time1 = System.currentTimeMillis();

        for(int i = 1; i <=1000;i++) {

            String connectionString = "jdbc:mysql://localhost:3306/drink?user=root&password=0000&maxPoolSize=100&pool";

         

            try (Connection connection = DriverManager.getConnection(connectionString)) {

                try (Statement stmt = connection.createStatement()) {

                    ResultSet rs = stmt.executeQuery("SELECT * FROM history LIMIT 5,5");

                    rs.next();

                }

            }     

        }

        time2 = System.currentTimeMillis();

        time3 = time2-time1;

        System.out.println("花了:" + time3 + " ms");

     

     

     

     

//**********************************

// mariaDB pool (best)

//**********************************

//所有參數都在JDBC的連結中,而且增加了"&maxPoolSize=100",設定pool大小是100

     

        time1 = System.currentTimeMillis();



        for(int i = 1; i <=1000;i++) {

        MariaDbPoolDataSource pool = new MariaDbPoolDataSource("jdbc:mysql://localhost:3306/drink?user=root&password=0000&maxPoolSize=100");

            try (Connection connection = pool.getConnection()) {

                try (Statement stmt = connection.createStatement()) {

                    ResultSet rs = stmt.executeQuery("SELECT * FROM history");

                    rs.next();

                    //System.out.println(i +  "\t" + rs.getString(2) );

                }

                connection.close();

            }

        }

        time2 = System.currentTimeMillis();

        time3 = time2-time1;

        System.out.println("花了:" + time3 + " ms");

     



    }



}

LINE機器人傳sticker貼圖

這一篇介紹如何讓LINE機器人辦識使用者傳來的sticker,並回傳sticker給使用者。



stickers可以讓對話更有趣、更生動。





---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

以下是PHP程式碼,先將所有會用到的sticker列出來,然後判斷LINE傳來的資料型態是否為sticker,然後再隨機挑一個一開始列出來的sticker,回傳給使用者。



程式碼


$_CONFIG['arrStickerNum']['1'] = array(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,21,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430);

$_CONFIG['arrStickerNum']['2'] = array(18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527);

$_CONFIG['arrStickerNum']['11537'] = array(52002734,52002735,52002736,52002737,52002738,52002739,52002740,52002741,52002742,52002743,52002744,52002745,52002746,52002747,52002748,52002749,52002750,52002751,52002752,52002753,52002754,52002755,52002756,52002757,52002758,52002759,52002760,52002761,52002762,52002763,52002764,52002765,52002766,52002767,52002768,52002769,52002770,52002771,52002772,52002773);

$_CONFIG['arrStickerNum']['11538'] = array(51626494,51626495,51626496,51626497,51626498,51626499,51626500,51626501,51626502,51626503,51626504,51626505,51626506,51626507,51626508,51626509,51626510,51626511,51626512,51626513,51626514,51626515,51626516,51626517,51626518,51626519,51626520,51626521,51626522,51626523,51626524,51626525,51626526,51626527,51626528,51626529,51626530,51626531,51626532,51626533);



$_CONFIG['token'] = '';



include ('lineJSONmaker.class.php');

class LineJSONmaker3 extends LineJSONmaker{

public function setSticker($packageId, $stickerId){


$json = <<
{

"type": "sticker",

"packageId": "$packageId",

"stickerId": "$stickerId"

}

EOT;

$this->arrJSON[] = $json;

}



}



$raw = file_get_contents('php://input');

$data = json_decode($raw, true);



$objLineJSON = new LineJSONmaker3($_CONFIG['token'], $data['events'][0]['replyToken']);

if($data['events'][0]['message']['type'] == 'sticker')

{

$keysPackage = array_keys($_CONFIG['arrStickerNum']);

$indexPackage = rand(0,count($keysPackage)-1);

$packageId = $keysPackage[$indexPackage];

$indexSticker = rand(0,count($_CONFIG['arrStickerNum'][$packageId])-1);

$stickerId = $_CONFIG['arrStickerNum'][$packageId][$indexSticker];

$objLineJSON->setSticker($packageId,$stickerId);

}

$result = $objLineJSON->send();





public function setSticker($packageId, $stickerId){

$json = <<
{

"type": "sticker",

"packageId": "$packageId",

"stickerId": "$stickerId"

}

EOT;

$this->arrJSON[] = $json;

}

?>









 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------




下列是LINE官方文件所列出的sticker代號表,下面列出我會用到的部份。

Sticker列表








































2020年1月13日 星期一

LINE的quickReply

設計LINE機器人時,有些固定的輸入可以使用泡泡選單呈現,在LINE的JSON語法裡它叫quickReply。




下面是quickReply的JSON格式:

{
"replyToken": "",
"messages": [
{
"type": "text",
"text": "測試泡泡選單。",
"quickReply": {
"items": [
{
"type": "action",
"action": {
"type": "message",
"label": "泡泡1",
"text": "泡泡1"
}
},
{
"type": "action",
"action": {
"type": "message",
"label": "泡泡2",
"text": "泡泡2"
}
},
{
"type": "action",
"action": {
"type": "message",
"label": "泡泡3",
"text": "泡泡3"
}
}
]
}
}
]
}





若當初建立LINE的channel時,是選Developer Trial,你就可以用下列語法PUSH訊息到LINE,把換成你的,要從webhook收到的JSON取得,最後加入上面的quickReply的JSON,就可以主動發送了。

curl -v -X POST https://api.line.me/v2/bot/message/push \ -H 'Content-Type:application/json' \ -H 'Authorization: Bearer {}' \ -d '{ "to": "", }'



也可以在webhook收到訊息後,用reply的格式回傳,參考下列文章。

參考本部落格的LINE機器人:
https://oscarhsu70.blogspot.com/2020/01/line.html

參考官方文件:
https://developers.line.biz/en/docs/messaging-api/using-quick-reply/











LINE官網機器人申請過程

第一次開發LINE機器人,需要LINE develop的帳號,請參考下列連結,建立開發者帳號。

https://developers.line.biz/en/docs/messaging-api/getting-started/


重要提醒! 一定要驗證你的email,LINE機器人才能正常工作。




先建立channel




選Messaging API


輸入你的機器人的設定,來建立你的LINE機器人。包含它的大頭照(App icon),機器人的名字(App name)。



這裡我們選Developer Trial,為了多一個PUSH MESSAGES的功能,可以主動發訊息給別人,需不是只能Reply(回應訊息)。Category及Subcategory選擇你的分類。Email是有其它事的話,可以連絡到你。




完成剛才的建立APP後,再進入你的APP繼續設定。



把下列4個地方設定好:

第一個Channel access token (long-lived),小時數預設0就好,表示這個Token沒有時間限制。

第二個Use webhooks,把它設定成Enabled。如果等下自己的PHP程式出錯,Use webhooks會自動調成Disable,你就要再回來改成Enable。

第三個Webhook URL,把你的Webhook server網址設定進去,如果你是第一次接觸Webhook,可參考Facebook Messenger API使用方法,裡面有對Webhook作解釋。但在設定這一項之前,你可能需要先建好你的Webhook程式網頁,不然它會回應失敗,這在下一段會講。

第四個Auto-reply messages,把它Disable掉,使用者才不會收到預設回應訊息。




建立下列PHP程式網頁,放在你的Server上,我喜歡設定檔名為webhook.php,存取網址要如同你上面設定的一樣。"" 要改成剛才ISSUE出來的Channel access token。記得建一個webhook.txt,放在這個程式檔的旁邊,記錄傳來的Webhook事件,到時候有問題,你可以自己打開來看發生什麼事。




    $_CONFIG['token'] = '';



    $logName = 'webhook.txt';



    $raw = file_get_contents('php://input');

    $data = json_decode($raw, true);

    rewrite($logName, "\n\n");

    rewrite($logName, "\n".$_SERVER['REQUEST_URI']);

    rewrite($logName, "\n".$raw);

    receiveMsg($data['events'][0]);



    function receiveMsg($messaging)

    {

        global $logName;

        $result = array();

        $result = sendText($messaging['replyToken']

        , 'Text received, echo: '.$messaging['message']['text']);

     

        rewrite($logName, "\n".$result[0].' : '.$result[1]);

    }



    function sendText($receive,$text){

        global $logName;

        $url = 'https://api.line.me/v2/bot/message/reply';



        $data['replyToken'] = $receive;

        $data['messages'] = array(array( 'type' => 'text', 'text' => $text) );

        $json = json_encode($data);

        rewrite($logName, "\n".$json);

        //rewrite($logName, "\n".'before http_post_data: '.$json);

        $result = http_post_data($url, $json);



        return $result;

    }

    function http_post_data($url, $data_string) {

            global $_CONFIG;

        //$token = $_CONFIG['token'];



        $ch = curl_init();



        curl_setopt($ch, CURLOPT_POST, 1);



        curl_setopt($ch, CURLOPT_URL, $url);



        curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);



        curl_setopt($ch, CURLOPT_HTTPHEADER, array(



            'Content-Type: application/json; charset=utf-8' ,

            'Authorization: Bearer {'.$_CONFIG['token'].'}' ,

            'Content-Length: ' . strlen($data_string))



        );



        ob_start();



        curl_exec($ch);



        $return_content = ob_get_contents();



        ob_end_clean();

        $return_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);



        return array($return_code, $return_content);



    }



    function rewrite($filename, $data)

    {

        $filenum=fopen($filename,'a');

        fwrite($filenum,$data);

        fclose($filenum);

    }







從剛才設定畫面的最下面,找到QRcode,用你自己的手機的LINE加入好友,就可以開始測試。





對話結果就如同下面










LINE-BOT-SDK套件(Python)介紹

Line提供Python的套件,給Python程式設計者可以使用套件API跟LINE互動,就不需要撰寫太多複雜的程式及JSON傳輸格式。套件是以Flask提供服務,啟動後,就是LINE的Webhook。



下載套件方法:

pip install line-bot-sdk







範例程式碼,可以Echo使用者輸入的字句:





from flask import Flask, request, abort

from linebot import (
LineBotApi, WebhookHandler
)
from linebot.exceptions import (
InvalidSignatureError
)
from linebot.models import (
MessageEvent, TextMessage, TextSendMessage,
)

app = Flask(__name__)
userNo = 1
line_bot_api = LineBotApi('')
handler = WebhookHandler('')

@app.route("/")
def hello():
 return "Hello World!"

@app.route("/callback", methods=['POST'])
def callback():
  # get X-Line-Signature header value
  signature = request.headers['X-Line-Signature']
  # get request body as text
  body = request.get_data(as_text=True)
  app.logger.info("Request body: " + body)

  #handle webhook body
  try:
   handler.handle(body, signature)
  except InvalidSignatureError:
   abort(400)
 
  return 'OK'


@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
if (event.reply_token == '00000000000000000000000000000000'):
return None
global userNo
text_message = TextSendMessage(text='Hello, world',
quick_reply=QuickReply(items=[
QuickReplyButton(action=MessageAction(label="label", text="text"))
]))
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text=userNo))
userNo += 1

 

if __name__ == "__main__":
app.run(
host = '0.0.0.0',
port = 12345,
debug = True,
ssl_context ='adhoc'

)

IKEA吊櫃廚櫃

 好不容易裝好IKEA買來的吊櫃,花了三天。 從組裝,鑽牆,上牆調水平,累死我了。