2020年1月31日 星期五

模式切換

在chatbot中,我們需要用到模式切換,而且不是用NLP處理,我們就需要寫一個中介的程式去處理這個工作。以下程式會詢問模式並記錄下來,10分鐘後會重問。此程式不需資料庫,直接記在檔案裡。

--------------------------------------------------------------------------------------------------------------------------------
$timeLimit = 600; // 保持模式的最長時間
$arrModeName = array();
$arrModeName[0] = '哈囉,這裡是入口,您想要處理務還是詢問相關問題? ';
$arrModeName[2] = '處理務';//務模式
$arrModeName[3] = '詢問相關問題';//問題模式

function router($message)
{
global $timeLimit;
global $arrModeName;

if($message['message']['text'] == $arrModeName[2])
{
setUserMode($message['source']['userId'],2);
}
else if($message['message']['text'] == $arrModeName[3])
{
setUserMode($message['source']['userId'],3);
}

list($mode,$time) = getUserMode($message['source']['userId']);
if(time() - $time > $timeLimit)
{//超過時間就重設為詢問模式
$mode = 1;
setUserMode($message['source']['userId'],$mode);
}

if($mode == 1)
{//詢問模式
echo $arrModeName[0];

}else if($mode == 2){
//傳給務模組
receiveMsg($message);

}
else if($mode == 3)
{//傳給問題模組
receiveMsg2($message);
}else
{
echo ("\n ERROR MODE");
}
}

function setUserMode($user,$mode)
{
$filename = './user/'.$user;
$strWrite = $mode.':'.time();
$filenum=fopen($filename,'w');
if(fwrite($filenum,$strWrite) === FALSE)
echo "Cannot write to file ($filename)";
fclose($filenum);
}

function getUserMode($user)
{
$filename = './user/'.$user;
if(file_exists($filename) ){
$filenum=fopen($filename,'r');
$str = fread($filenum,1);
fread($filenum,1);
$time = fread($filenum,10);
fclose($filenum);
$mode = intval($str);
$time = intval($time);
$strWrite = $mode.':'.time();
$filenum=fopen($filename,'w');
if(fwrite($filenum,$strWrite) === FALSE)
echo "Cannot write to file ($filename)";
}
else
{
$time = time();
$mode = 1;
$filenum=fopen($filename,'w');
$strWrite = $mode.':'.$time;
if(fwrite($filenum,$strWrite) === FALSE)
echo "Cannot write to file ($filename)";
chmod($filename,0666);
}
fclose($filenum);
return array($mode,$time);
}
?>














LINE機器人範例


中國信託LINE

  • 選單是圖片,指定座標範圍當按鈕
  • 訊息是圖片,點進去是網頁,看細節
  • 美工圖片和自然語言處理一樣重要!



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









卡米狗 - 各大Line群已被攻佔! 《卡米狗》嗆爆所有人

單純的上下句配對,就能達到吸引人的效果。


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





Line@酒店攬客機器人

定時傳設計好的訊息
設計話語(資料庫?演算法?)



照片也很重要!


















chatbots如何改善印度的醫療狀況


chatbots能同時提供多個使用者服務,節省時間和成本。



chatbots己經成功幫助數百萬人說明他們的健康問題並提供適當的指導

  • 根據病患自己輸入的症狀,幫助歸納出自己的具體疾病
  • 幫助病患參與(了解、決定)自己的治療過程
  •  
  • 發送各種重要的提醒通知給病患

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

可以幫助使用者:

預約看診

醫院資訊

醫生資訊

疾病問題

利用選單幫助使用者Query



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

理解使用者要預約掛號。

接著問使用者的城市,利用Quick Reply做到。











2020年1月30日 星期四

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('k2j34l2kj34l2kj34l2kh143;kljh51;kl2h5;l1k2hj;45lkh12;l3k4h2kj3hn4kj2lh34lkj2h34lk2jh34lkjh1242kl3jh4lk2jh34lk2jh34lk1jh243lkjh2lk3j4hlk23h4') handler = WebhookHandler('kj2h34lkj2h433lk2jh4lk3j2h4') @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' )








Flask+HTTPS

如果直接用Python+Flask,提供REST API的服務,只會開啟http://127.0.0.1:5000的Daemon給別人呼叫,但現在流行用HTTPS,很多Webhook功能都強迫你要能提供HTTPS的通訊協定給它呼叫,故我上網查怎麼讓Flask支援SSL。

只要在你的app.run()裡,加上 ssl_context ='adhoc' 就可以了,如下列Python程式片段:

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



上述用到adhoc功能,需要pyOpenSSL套件。但你如果有申請SSL憑證,就不需要這個套件。



有些Client要求你一定要用合法的憑證,否則就不連,甚至連用Postman測試HTTPS的API,也要求這樣,否則不幫你測(或到setting裡把SSL certificate verification)。

若你有合法的憑證,把ssl_context 改成下列參數就能用:

ssl_context =('/PATH/TO/SSL.CRT','/PATH/TO/SSL.KEY')

聊天機器人(Chatbot)的程式架構

我們在設計聊天機器人(Chatbot)時,直覺會用傳統的 「網路程式語言+資料庫」,例如PHP+MySQL,而PHP會用到資料庫的原因,除了要存一些長期用到的資料,還有短期的資料。所謂短期是指幾分鐘或幾秒,甚至以毫秒計算,例如一天的網站累計進站人次,一般常把累計值存在資料庫裡。

短期資料的問題是會造成經常性的資料庫存取,使得資料庫來不及反應,進而需要調整資料庫的參數、換更好的電腦,甚至需要Load Balance。

如果我們回到原點想一個機器人應該有的程式架構,就會發現為什麼不是程式自己記得前幾秒的資料,而是需要額外的資料倉儲機制。而原因也很簡單,因為像PHP之類的Web程式,用一次就關掉了,Process就消失了,它根本不記得幾秒鐘之前的request已經服務了什麼事,所以又額外需要一個資料庫,幫忙記東西。

我們希望程式能記得前一回的事情,就不能用傳統的架構。程式啟動後,會成為Process,這時我們應該要保持住這個Process,不能讓它被關掉,如果是一般Web程式寫法一定不行。

解決的方法就是,程式要能自己運行(例. 在command line呼叫起來且不會結束),程式會不斷的迴圈,監聽有沒有連線進來(例. 開Socket),有就開始處理、回傳訊息,然後再繼續監聽。利用程式的Global variable記憶短期的資料,就不需要存取資料庫。

自己運行也不需要一直initial程式,節省Initial時間,加快服務速度。如果需要資料庫存資料,也可以一直保持連線,節省communication成本。

要注意的是,記在Global variable短期的資料不能太多,否則會memory overflow,記憶體會不夠用。如果需要記的短期資料太多,可以存到資料庫或檔案。也可以增加電腦作Load Balance。

Restful的LOG


Flask預設就有log功能,但是不會直接存到外面的檔案,若需要存到LOG檔,就需要import logging來支援。

Flask產生LOG訊息,只需呼叫app.logger,有三種LOG項目: Debug、Warning、Error。

若要指定LOG檔,需要產生FileHandler,"logging.FileHandler('flask.log') "。

如下範例:


from flask import Flask
import logging

app = Flask(__name__)
@app.route('/')
def root():
  app.logger.debug('A value for debugging')
  app.logger.warning('A warning occurred (%d apples)', 42)
  app.logger.error('An error occurred')
if __name__ == '__main__':
  app.logger.addHandler(  logging.FileHandler('flask.log') )
 app.run( debug=True )

Restful的錯誤處理

Restful在通訊的過程,發生錯誤時,可以經由http的header知道錯誤,也有錯誤代碼可以判讀,如果要從程式引起,或客制化錯誤回應,接受方只要讀取http的標頭,就可以知道成功或失敗、失敗的原因為何。程式碼如下:


from flask import Flask, request, abort

app = Flask(__name__)

@app.route('/login')
def login():
    abort(404)

@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404

if __name__ == "__main__":
  app.run(debug=True)





多話的虛擬客服

虛擬客服應用自然語言處理的技術,讓使用者可以用自己的話來詢問機器人,用語言和機器人自然的互動,但技術的必要性常被質疑,"用按鈕介面按一按、選一選就好了,不需要用到技術複雜、成本又高的NLP" "用任務導引方式,引導使用者輸入關鍵字完成任務就好",故NLP常被認為不必要,儘管我們努力讓機器人理解語言的能力有多精確。

我看了一些對話型助理的例子,包含實際的試作成果和科幻影片所呈現的,發現除了精確的理解句子以外,滿足使用者話中的隱喻、需求,更能表現出虛擬客服的價值,也就是你的回應不能只單單回應使用者字面上的意思,還要講一些相關的話,而使用者會感覺只要講一點簡單的話,就可以得到豐富的回應。如下面的例子:

Q.早安
A.(動作-自動開窗)早安,今天是星期六,你有5個會議,室溫設定到"涼爽",20度。

Q.現在幾點?
A.現在是12點,你要吃午餐了嗎? 有幾間推薦、你收藏的餐廳可以試看看,.....

Q.我要訂高鐵下午3點達新竹。
A.班次1314,14:30從台北發車,15:00到新竹,標準車廂如果要搭區間車往市區,15:05及15:20有車可以坐。要訂嗎?


參考文獻: https://youtu.be/ZGLPxEv_EWo

LINE機器人加入群組發系統通知

我將LINE機器人邀請加入我們的群組,讓它可以在系統有問題時,發通知給大家。LINE機器人要加入群組,必需被邀請才能,故先用自己的LINE帳號邀請。Script中,"Authorization: Bearer {"後的亂碼是LINE_CHANNEL_TOKEN,要換成自己LINE機器人的,"to :"後面的亂碼是GROUP_ID,可以從webhook收到群組發訊息,從LOG中取得。"text :"後面接的就是你要主動推送的訊息。



這個功能可以用在系統錯誤通知,也可以用在埋樁在各個群組中,推銷產品。







Deep Q-network


Deep Q Network 的簡稱叫DQN, 是將Q learning 的優勢和Neural networks 結合了. 如果我們使用tabular Q learning, 對於每一個state, action 我們都需要存放在一張q_table 的表中. 如果像顯示生活中, 情況可就比那個迷宮的狀況復雜多了, 我們有千千萬萬個state, 如果將這千萬個state 的值都放在表中, 受限於我們電腦硬體, 這樣從表中獲取數據, 更新數據是沒有效率的. 這就是DQN 產生的原因了. 我們可以使用神經網絡來估算這個state 的值, 這樣就不需要一張表了.



整個算法乍看起來很複雜, 不過我們拆分一下, 就變簡單了. 也就是個Q learning 主框架上加了些裝飾.

這些裝飾包括:
  • 記憶庫(用於重複學習)
  • 神經網絡計算Q 值
  • 暫時凍結q_target參數(切斷相關性)



Laravel, the PHP web framework

Laravel是一個PHP web framework,它可以經由幾個簡單的指令,完成一個漂亮的網站。

依官方網站的說明,安裝好後,只需執行下列指令,就能完成一個簡單的網站:

laravel new blog
上面指令會產生一個blog目錄,到目錄裡執行下列指令,就會啟動網站:

php artisan serve
接著開Browser,輸入下列網址就能看到網站

http://localhost:8000


也可以用虛擬機安裝方式,安裝Homestead ,就能一次裝好所有需要的環境,包含下列軟體:

  • Ubuntu 16.04
  • Git
  • PHP 7.1
  • Nginx
  • MySQL
  • MariaDB
  • Sqlite3
  • Postgres
  • Composer
  • Node (包含 Yarn, PM2, Bower, Grunt, and Gulp)
  • Redis
  • Memcached
  • Beanstalkd
在啟動 Homestead 環境之前,必需先安裝 VirtualBox 5.1,VMWare,或 Parallels 以及搭配 Vagrant。

執行下列指令,就能自動將Homestead安裝到本機:

vagrant box add laravel/homestead
git clone https://github.com/laravel/homestead.git Homestead
bash init.sh




範圍型時間詞的自然語言處理


範圍型時間詞(例下午、下個月) 的自然語言處理 和 時間點的詞(下午3點、明天) 處理方式不一樣,範圍型時間詞轉換成電腦可處理的時間,需輸出兩個時間點,一個是啟始時間,一個是結束時間,我以如下範例說明範圍型時間詞轉換結果:

"下午" 的轉換結果

{

  "start" : {

  "unixtime": 1554872400,

  "hour": 13,

  "minute": 0,

  "second": 0

  },

  "end" : {

  "unixtime": 1554963123,

  "hour": 18,

  "minute": 0,

  "second": 0

  }

}

下個月的轉換結果

{

  "start" : {

  "unixtime": 1554872400,

  "year": 2019,

  "month": 5,

  "day": 1

  },

  "end" : {

  "unixtime": 1554963123,

  "year": 2019,

  "month": 5,

  "day": 31

  }

}




搬資料表並修改欄位的shell script


以下的LINUX shell script可以搬資料表並修改欄位



#!/bin/bash
echo "start..."
#DUMP遠端資料表2個,不用在Inser資料時LOCK,不然LOCK來不及放掉,其它Client就要等很久。
mysqldump --skip-add-locks -h HOST_IP -u USERNAME -pPASSWORD DBNAME TABLENAME1 TABLENAME2 > /tmp/BACKUP.sql
#搜尋一些東西,替代掉
sed -i -e 's/TABLENAME/NEW_TABLENAME/g' /tmp/BACKUP.sql
sed -i -e 's/utf8mb4_unicode_ci/utf8_general_ci/g' /tmp/BACKUP.sql
sed -i -e 's/utf8mb4/utf8/g' /tmp/BACKUP.sql
sed -i -e 's/InnoDB/MyISAM/g' /tmp/BACKUP.sql
sed -i -e 's/`Query` text COLLATE utf8_general_ci COMMENT/`Query` text COLLATE utf8_general_ci NOT NULL DEFAULT "" COMMENT/g' /tmp/BACKUP.sql

#導入資料表到新的資料庫
mysql -u username -pPassword DBNAME < /tmp/BACKUP.sql

#修改一些資料表格式,剛才沒替代掉的
mysql -u username -pPassword -e "ALTER TABLE mytablename CHANGE colname newcolname CHAR(191)_type" DBNAME
mysql -u username -pPassword -e "ALTER TABLE mytablename MODIFY COLUMN colname VARCHAR(191)_newtype" DBNAME
echo "finish"











日期範圍詞 正規化


  • 日期範圍詞 正規化目前可處理下列Regular Expression所接受的關鍵字:
r"(上|下)(週|周|禮拜|星期)"
Ex. 上星期
r"(明年|去年|前年)?([零一二兩三四五六七八九十0123456789]+)月"
Ex. 明年四月
r"(上|下)個?月"
Ex. 上個月
------------------------------------------------------------------------------------------------------------------------
  • 回應如下:
    "start": {
      "year": 2020,
      "month": 2,
      "day": 1
    },
    "end": {
      "year": 2020,
      "month": 2,
      "day": 29
    }
------------------------------------------------------------------------------------------------------------------------

  • 處理方法:


def normDateRange(strDate):

          strDate = wf2nf.wf2nf(strDate)

          d = datetime.datetime.now()

          ts = int(d.timestamp())

          oneDay = 86400  # 24*60*60

          listMonthWith31day = [1, 3, 5, 7, 8, 10, 12]

          listMonthWith30day = [4, 6, 9, 11]

 

          theYear = d.year

          theDay = d.day

          theMonth = d.month

          theWeekday = d.weekday()

 

          regex = r"(這|上|下)?週末"

          match = re.search(regex, strDate, re.MULTILINE)

          if match != None:

                   day2Weekend = 6 - theWeekday

                   tsEnd = ts + day2Weekend * oneDay

                   tsStart = tsEnd - oneDay

                   if match.group(1) == "上":

                             tsStart -= 7 * oneDay

                             tsEnd   -= 7 * oneDay

                   elif match.group(1) == "下":

                             tsStart += 7 * oneDay

                             tsEnd   += 7 * oneDay

                   dtStart = datetime.datetime.fromtimestamp(tsStart)

                   dtEnd = datetime.datetime.fromtimestamp(tsEnd)

                   return [dtStart.year, dtStart.month, dtStart.day, dtEnd.year, dtEnd.month, dtEnd.day]

 

          regex = r"(這|上|下)(週|周|禮拜|星期)"

          match = re.search(regex, strDate, re.MULTILINE)

          if match != None:

                   if match.group(1) == "上":

                             tsEnd = ts - (theWeekday + 1) * oneDay  # last sunday

                             tsStart = tsEnd - (6 * oneDay)  # last monday

                   elif match.group(1) == "下":

                             day2Weekend = 7 - theWeekday

                             tsStart = ts + day2Weekend * oneDay

                             tsEnd = tsStart + ( 6 * oneDay )

                   elif match.group(1) == "這":

                             tsStart = ts - (theWeekday * oneDay )

                             tsEnd = tsStart + ( 6 * oneDay )

                   else:

                             return [-1]

                   dtStart = datetime.datetime.fromtimestamp(tsStart)

                   dtEnd = datetime.datetime.fromtimestamp(tsEnd)

                   return [dtStart.year, dtStart.month, dtStart.day, dtEnd.year, dtEnd.month, dtEnd.day]

 

          regex = r"(明年|去年|前年)?([零一二兩三四五六七八九十0123456789]+)月"

          match = re.search(regex, strDate, re.MULTILINE)

          if match != None:

                   if match.group(1) == "明年":

                             theYear += 1

                   elif match.group(1) == "去年":

                             theYear -= 1

                   elif match.group(1) == "前年":

                             theYear -= 2

                   theMonth = chinese_to_arabic2(match.group(2))

                   if theMonth in listMonthWith31day:

                             result = [theYear, theMonth, 1, theYear, theMonth, 31]

                   elif theMonth in listMonthWith30day:

                             result = [theYear, theMonth, 1, theYear, theMonth, 30]

                   elif theMonth == 2:

                             ts = datetime.datetime(year=theYear, month=(theMonth+1), day=1).timestamp()

                             d2 = datetime.datetime.fromtimestamp(ts - oneDay)

                             result = [theYear, theMonth, 1, theYear, theMonth, d2.day]

                   else:

                             result = [-1]

                   return result

 

          regex = r"下個?月"

          match = re.search(regex, strDate, re.MULTILINE)

          if match != None:

                   theMonth += 1

                   if theMonth in listMonthWith31day:

                             lastDay = 31

                   elif theMonth in listMonthWith30day:

                             lastDay = 30

                   elif theMonth == 2:

                             ts = datetime.datetime(year=theYear, month=(theMonth+1), day=1).timestamp()

                             d2 = datetime.datetime.fromtimestamp(ts - oneDay)

                             lastDay = d2.day

                   elif theMonth == 13:

                             theYear += 1

                             theMonth = 1

                             lastDay = 31

                   else:

                             return [-1]

                   return [theYear, theMonth, 1, theYear, theMonth, lastDay]

 

          regex = r"上個?月"

          match = re.search(regex, strDate, re.MULTILINE)

          if match != None:

                   theMonth -= 1

                   if theMonth in listMonthWith31day:

                             lastDay = 31

                   elif theMonth in listMonthWith30day:

                             lastDay = 30

                   elif theMonth == 2:

                             ts = datetime.datetime(year=theYear, month=(theMonth+1), day=1).timestamp()

                             d2 = datetime.datetime.fromtimestamp(ts - oneDay)

                             lastDay = d2.day

                   elif theMonth == 0:

                             theYear -= 1

                             theMonth = 12

                             lastDay = 31

                   else:

                             return [-1]

                   return [theYear, theMonth, 1, theYear, theMonth, lastDay]

 

          return [-1]




產生台灣IP清單給iptables防火牆


這個Python程式可以線上將TWNIC的台灣IP清單,轉成iptables的指令,貼到/etc/iptables.rules上,就可以限制只能台灣的IP能存取。

限制port時,用如下寫法,port 80使用"TaiwanIP"規則(平常我們是寫ACCEPT):

-A INPUT -p tcp -m tcp --dport 80 -j TaiwanIP

所以你的iptables.rules裡會先有這些描述:

:INPUT DROP [27:1852]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [164:21816]
:TaiwanIP - [0:0]

如果是更改作用中的防火牆,用以下指令加入:

iptables -N TaiwanIP

設定好iptables.rules後,用如下指令讓新規則產生做用:

sudo iptables-restore < /etc/iptables.rules
P.S. 有些單位的IP範圍不在清單上,好像是比較敏感的單位。


程式碼

# -*- coding: utf-8 -*-
'''
產生台灣IP清單及iptables指令
'''
import urllib.request
import pandas as pd
import ipaddress
from bs4 import BeautifulSoup

response = urllib.request.urlopen('https://rms.twnic.net.tw/twnic/User/Member/Search/main7.jsp?Order=ORG.ID')
html = response.read().decode(response.headers.get_content_charset())
soup = BeautifulSoup(html, 'html.parser')
listTagTable = soup.find_all('table')
df = pd.read_html( str(listTagTable[5]),header=0 )[0]

def getIP(x):
ipStart, ipEnd = x.split(' - ')
ipStartInt = int(ipaddress.IPv4Address(ipStart))
ipEndInt = int(ipaddress.IPv4Address(ipEnd))
return pd.Series([ipStart, ipEnd, ipStartInt, ipEndInt], index=['ipStart', 'ipEnd', 'ipStartInt', 'ipEndInt'])
df2 = df.iloc[:,3].apply( getIP ) #只一行Series,可以不指定axis

df['ipStart'] = 0
df['ipEnd'] = 0
df['ipStartInt'] = 0
df['ipEndInt'] = 0
df[ ['ipStart', 'ipEnd', 'ipStartInt', 'ipEndInt'] ] = df2
df = df.sort_values(by=['ipStartInt'])
dicMerge = {'name':[df.iloc[0, 0]]
, 'ID':[df.iloc[0, 1]]
, 'ipStart':[df.iloc[0, 5]]
, 'ipEnd':[df.iloc[0, 6]]
, 'ipStartInt':[df.iloc[0, 7]]
, 'ipEndInt':[df.iloc[0, 8]] }
rowN, colN = df.shape
dicMergeIndex = 0
#下列For是將IP範圍相鄰的合併,如此只需一組
for i in range(1,rowN):
if (df.iloc[i, 7] - dicMerge['ipEndInt'][dicMergeIndex]) == 1:
dicMerge['ipEndInt'][dicMergeIndex] = df.iloc[i, :]['ipEndInt']
dicMerge['ipEnd'][dicMergeIndex] = df.iloc[i, :]['ipEnd']
else:
dicMerge['name'].append(df.iloc[i, 0])
dicMerge['ID'].append(df.iloc[i, 1])
dicMerge['ipStart'].append(df.iloc[i, 5])
dicMerge['ipEnd'].append(df.iloc[i, 6])
dicMerge['ipStartInt'].append(df.iloc[i, 7])
dicMerge['ipEndInt'].append(df.iloc[i, 8])
dicMergeIndex += 1
dfTaiwanIP = pd.DataFrame(dicMerge)

rowN2, colN2 = dfTaiwanIP.shape
with open('TaiwanIP.txt', 'w', encoding='utf8') as f:
for i in range(0, rowN2):
f.write("-A TaiwanIP -m iprange --src-range %s-%s -j ACCEPT\n" % (dfTaiwanIP.iloc[i]["ipStart"], dfTaiwanIP.iloc[i]["ipEnd"]) )
#上述會產生一連串的iptables指令到TaiwanIP.txt這個檔案,直接貼到/etc/iptables.rules。但我是寫給"TaiwanIP" chain,故前面要有":TaiwanIP - [0:0]"





基於Hetero-ConvLSTM的交通風險預測






參考: Yuan, Zhuoning, Xun Zhou, and Tianbao Yang. "Hetero-convlstm: A deep learning approach to traffic accident prediction on heterogeneous spatio-temporal data." Proceedings of the 24th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining. ACM, 2018.

三角形知三邊長求高


已知三角形三個邊的長,求三角形高度h,如下圖:


a,b,c是已知的三個邊,x是h的垂直交點左邊的長度,x是未知。



定理: a^2-x^2 = h = b^2 - (c-x)^2

x = (a^2 - b^2 + c^2) / 2c

所以h=a^2-( (a^2 - b^2 + c^2) / 2c )^2


gunicorn無法啟動



裝好conda的環境後,發現gunicorn怎麼都沒辦法啟動,直接跑指令如下,出現錯誤訊息。



$ gunicorn -c conf/gunicorn_conf.py -p RUNPID_1234 -b 0.0.0.0:1234 webhook:app --certfile=server.crt --keyfile=server.key

Error: class uri 'gthread' invalid or not found:

[Traceback (most recent call last):
File "/home/3333/.conda/envs/shopbot/lib/python3.6/site-packages/gunicorn-19.1.0-py3.6.egg/gunicorn/util.py", line 130, in load_class
section, uri)
File "/home/3333/.conda/envs/shopbot/lib/python3.6/site-packages/pkg_resources/__init__.py", line 570, in load_entry_point
return get_distribution(dist).load_entry_point(group, name)
File "/home/3333/.conda/envs/shopbot/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2686, in load_entry_point
raise ImportError("Entry point %r not found" % ((group, name),))
ImportError: Entry point ('gunicorn.workers', 'gthread') not found



Google完知道是BUG,但沒有說怎麼解。

查版本發現我的Gunicorn是舊版19.1,我是從conda install gunicorn裝的。

移除舊版,改用pip install gunicorn,裝新版後才正常啟動,真的是浪費我很多時間~

以後都用pip裝python套件,不用conda install裝了。

2020年1月29日 星期三

An Efficient Trajectory Compression Algorithm for Moving Objects

這篇介紹壓縮GPS軌跡點的演算法,這篇的PATCH演算法改良自 SQUISH-E :

J. Muckell, P. Olsen, J. Hwang, C. Lawson, and S. Ravi,“Compression of trajectory data: a comprehensive evaluation and new approach.”Geoinformatica, vol. 18, no. 3, pp. 435-460, 2014

PATCH壓縮方式是截彎取直,同時有最小的誤差,不會和原來軌跡的形狀差太多。

壓縮率計算方法: R=(1-(a/b)) x 100%



下列是PATCH演算法總覽,其中Filtration()、Divergence_Identification()、Neighbour_Calculation()分成三個Algorithm描述。


 Filtration利用Sliding Window的方式,一個一個GPS point塞到priority queue,達到最大容量時,還會繼續塞,但會把低priority的point移除。




Divergence_Identification會將前一個步驟(Filtration)的路線轉向關鍵point,決定保留或移除。



Neighbour_Calculation檢查Divergence_Identification 輸出的point數量,如果沒有達到參數的數量,就重跑Filtration,將之前過濾掉的拿出來再重新考慮,直到達到參數設定的數量。



























對話機器人和真人客服協作



常見方法,因AI訓練資料不足,先由真人客服和客戶對話,讓機器人批次學習,待機器人可以應付少數對話,再導入機器人處理設定好的對話任務。

1.真人客服和客戶對話 -> 訓練資料
2.對話機器人學習
3.對話機器人和客戶對話 -> Unknow和答得不好, 對話任務拋回到1
4.對話機器人取代真人客服


臉書買下以色列的聊天機器人新創Servicefriend
https://www.ithome.com.tw/news/133219

Chat-room 改成個別聊天室






Google Knowledge Graphs技術的應用


漫谈 Clustering (4): Spectral Clustering



http://blog.pluskid.org/?p=287

Psql 基本操作方式(一)


http://sql.onlinedoc.tw/2016/07/psql.html


安裝CKAN

CKAN 是著名的開放資料入口平台(Data Portal Platform)

因為原始安裝方法,問題太多,裝不進去,故改用docker-compose安裝,很順利的迅速裝好,以下說明如何安裝:



1.準備好專案目錄及git CKAN下來

cd /path/to/my/projects

git clone https://github.com/ckan/ckan.git



2. 將範例.env.template檔,複製成.env檔,並將.env裡的CKAN_SITE_URL參數改成連到CKAN的網址

cp contrib/docker/.env.template contrib/docker/.env



3. 輸入下列指令建立docker

cd contrib/docker
docker-compose up -d --build


4. 剛安裝ckan是沒有管理者,用下列指令在docker中建管理者,[your name] 換成你的帳號

docker exec -it ckan /usr/local/bin/ckan-paster --plugin=ckan sysadmin -c /etc/ckan/production.ini add [your name]

命令列執行的python程式 - 抓Json list超過時間的項目







本篇是寫一個在命令列執行的Python程式,功能是抓Json list裡超過基準時間的項目。這個程式特別在要保持可攜性,因為Python程式如果要求要一堆特別的套件,才能實現功能,交給同事使用時,就會很難正常使用。這個Python程式的原則是,只處理shell script不容易作到的事情,其它盡量在命令列模式下完成,使用時指令會像下列格式:

python parse.py list.json 2019-12-05T03:28:14Z > id.txt

輸入和輸出都是靠命令列完成,Python不用管太多。

以下Python程式還是要裝Python-dateutil或py-dateutil套件,但已經非常精簡,而且不分Python版本都能執行:

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

import sys
import json
import dateutil.parser

# 檔案名,此檔包含從ckan抓回來的,資料集內的檔案清單
jsonFile = sys.argv[1]

# 基準時間,時間格式是ISO 8601
baseTime = sys.argv[2]

# 將Dataset的資料JSON讀出來
with open(jsonFile, 'r') as f:
   try:
      jsonDatasetMeta = json.load(f)
   except:
      print('-1')
      print('JSON format error')
      exit(1)

# 將基準時間轉成時間變數
try:
   lastTime = dateutil.parser.parse(baseTime)
except:
   print('-1')
   print('base time error')
   exit(1)

if 'result' not in jsonDatasetMeta:
   print('-1')
   print('JSON file content error: result not in jsonDatasetMeta')
   exit(1)

if 'resources' not in jsonDatasetMeta['result']:
   print('-1')
   print('JSON file content error: resources not in jsonDatasetMeta')
   exit(1)

try:
   for file in jsonDatasetMeta['result']['resources']:
      fileCreated = dateutil.parser.parse(file['created'] + 'Z')
      if fileCreated > lastTime:
         print(file['url'])
except Exception as e:
   print('-1')
   print('File created time error.')
   print(e)
   exit(1)


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















2020年1月14日 星期二

Tesseract OCR


Tesseract OCR 是一套可以辨識照片中的文字的強大工具。
本篇文章介紹如何安裝及使用。

1.
在Linux安裝套件可以簡單的執行
sudo apt-get install tesseract-ocr
sudo apt-get install tesseract-ocr-chi-tra #中文語言包
sudo apt-get install tesseract-ocr-eng #英文語言包

2.
安裝好後,再到
自行下載最佳語言包,到下列語言包目錄
/usr/share/tesseract-ocr/5/tessdata
覆蓋舊的chi-tra及eng.traineddata


3.
執行方法:
tesseract sample.jpg sample -l chi_tra+eng --psm 1 --oem 1 alto
參數說明:
sample.jpg是你要辨識的照片
sample是輸出主檔名
chi_tra+eng是中文模型加英文模型(模型名稱可以參考這個模型清單https://github.com/tesseract-ocr/tessdata_best)
--psm 1是Automatic page segmentation with OSD
--oem 1是LSTM引擎
alto 是Output in ALTO format (OUTPUTBASE.xml).,類似hOCR的格式


4.
辨識的結果是XML格式,裡面包含了辨識的字和位置,要讀這個檔案的內容,可以用下列軟體:
http://www.prima.cse.salford.ac.uk/tools/PAGEViewer

開啟剛才完成的XML檔案和原始圖片,PageViewer會合併在一起秀給你看。將鼠標放在圖片的字上,就會秀出辨識的字串。


















簡潔啟動gunicorn_service.sh

本篇介紹將Gunicorn的gunicorn_service.py,再包一個shell script,讓服務啟動的指令簡潔一些。 原本python程式包好Gunicorn後,啟動方式如下: source activate normTime python gunicorn_service.py --port 1234 start source deactivate 如果要關閉服務,或是reload,也要照上面的指令,但start改成stop或reload。 我另外寫一個shell script,將上述指令包起來,並把變數抽出來,執行檔存為gunicorn_service.sh : #!/bin/bash PORT=1234 APPNAME=timeRestful ENVNAME=normTime source activate ${ENVNAME} python gunicorn_service.py --port ${PORT} --app ${APPNAME}:app $1 source deactivate 上述程式碼將port號碼、APP檔名、python虛擬環境名稱,抽出來,使更換服務時,方便修改。 gunicorn_service.py要加下列兩段程式碼,使"--app"參數可以用: -------------------------------------- parser.add_argument( '-a', '--app', metavar='APP', type=str, default='', help='Flask APP name with "....:app".' ) -------------------------------------- . . if args.app != '': self.config.app = args.app -------------------------------------- 啟動時,可以直接下./gunicorn_service.sh start ,停止服務./gunicorn_service.sh stop,重載服務./gunicorn_service.sh reload。 其實後面的start, stop, reload都是直接傳遞給gunicorn_service.py,故任何gunicorn_service.py可以接授的關鍵字,都可以用。

Flask用Route參數和Get參數比較

原本之前寫Python的Flask是用Route的方式,如下範例: http://server:50000/norm1/字串/norm2/字串 程式寫法如下: @app.route("/norm1//norm2/ ") def normAPcode(APstr,APstr2): 使用GET的方式取後參數,形式如下: http://server:50000?norm1=字串&norm2=字串 程式寫法如下: @app.route("/nenormalize") def nenormalize(): dicParam = request.args dicParam["norm1"] dicParam["norm2"] Route方式傳參數缺點 1.最大的缺點是,字串裡有"/"的話,會無法處理(用瀏覽器開時會被當成網址裡的路徑),也許寫程式時,用url encode可以避免,但變成不能用瀏覽器測試。 2.如果有多個參數要傳,就要寫死在Route上,如果有時候只想傳一個,就要另外再寫一個Route,跟GET傳參數比起來,GET可以可有可無,在程式碼內加個if判斷norm2 in dicParam就好。

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買來的吊櫃,花了三天。 從組裝,鑽牆,上牆調水平,累死我了。