2022-07-05

【PHP】LINE 回覆地理位置訊息

參考資料 ----
Location message

 
<?php
// 因為網站是寄放在虛擬主機商那, 所以要指定時區
date_default_timezone_set('Asia/Taipei');
$channelAccessToken = '{從 LINE 官方帳號後台取得的 token}';
 
// 客戶端傳來的訊息
$bodyMsg = file_get_contents('php://input');

// 寫 log, 檔案在目前目錄下的 log 目錄內
// 檔名為 log_西元年月日.log, 一天一個檔
$sLogfile = './log/log_'.date('Ymd').'.log';
$fp = fopen($sLogfile, "a+");
fwrite($fp , print_r(date('Y-m-d H:i:s').', Recive: '.$bodyMsg."\n", true));
fclose($fp);

$obj = json_decode($bodyMsg, true);

foreach ($obj['events'] as &$event) 
{
   $userId = $event['source']['userId'];
   
    // 取得 使用者傳來的訊息
    $msgUser = $event['message']['text'];

    // 回傳 地址位置訊息
    $payload = [ 'replyToken' => $event['replyToken'],
                'messages' => [ 
                                [
                                    "type" => "location",
                                    "title" => "高捷紅線美麗島站",
                                    "address" => "高雄市新興區中正四路3號",
                                    "latitude" => 22.631392,
                                    "longitude" => 120.301803
                                ]
                            ]
            ];
            
    // Send reply API
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, 'https://api.line.me/v2/bot/message/reply');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [  'Content-Type: application/json',
                                            'Authorization: Bearer ' . $channelAccessToken
                                        ]);
    $result = curl_exec($ch);
    curl_close($ch);
}
?>
 

 
相關筆記 ----

2022-07-02

【PHP】LINE 回覆影片訊息

參考資料 ----
Video message

 
<?php
// 因為網站是寄放在虛擬主機商那, 所以要指定時區
date_default_timezone_set('Asia/Taipei');
$channelAccessToken = '{從 LINE 官方帳號後台取得的 token}';
 
// 客戶端傳來的訊息
$bodyMsg = file_get_contents('php://input');

// 寫 log, 檔案在目前目錄下的 log 目錄內
// 檔名為 log_西元年月日.log, 一天一個檔
$sLogfile = './log/log_'.date('Ymd').'.log';
$fp = fopen($sLogfile, "a+");
fwrite($fp , print_r(date('Y-m-d H:i:s').', Recive: '.$bodyMsg."\n", true));
fclose($fp);

$obj = json_decode($bodyMsg, true);

foreach ($obj['events'] as &$event) 
{
   $userId = $event['source']['userId'];
   
    // 取得 使用者傳來的訊息
    $msgUser = $event['message']['text'];

    // 回覆 影片 訊息
    $payload = [ 'replyToken' => $event['replyToken'],
                'messages' => [ 
                                [
                                    "type" => "video",
                                    "originalContentUrl" => "https://完整網址/原始影片檔檔名.mp4",    // 大小不可超過 200M
                                    "previewImageUrl" => "https://完整網址/預覽圖檔檔名.png",   // 限 jpg 或 png, 大小不可超過 1M
                                    "trackingId" => "myid"  // 選擇性參數, 只可輸入 英數字 及 .=,+*()%$&;:@{}!?<>[] 這幾個符號
                                                            // 官網是說當回覆有帶這個參數時, 客戶端看完影片時會通知 webhook
                                ]
                            ]
            ];
            
    // Send reply API
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, 'https://api.line.me/v2/bot/message/reply');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [  'Content-Type: application/json',
                                            'Authorization: Bearer ' . $channelAccessToken
                                        ]);
    $result = curl_exec($ch);
    curl_close($ch);
}
?>
 


user 看完影片時, server 這邊收到的通知內容如下
 
"destination":"bla1bla1bla1bla1bla1bla1bla1bla1bla1bla1",
"events":[{ "type":"videoPlayComplete",
            "videoPlayComplete":{"trackingId":"myid"},
            "webhookEventId":"bla2bla2bla2bla2bla2bla2bla2bla2bla2bla2",
            "deliveryContext":{"isRedelivery":false},
            "timestamp":1656748409328,
            "source":{"type":"user",
                     "userId":"bla3bla3bla3bla3bla3bla3bla3bla3bla3bla3"
                    },
            "replyToken":"bla4bla4bla4bla4bla4bla4bla4bla4bla4bla4",
            "mode":"active"
        }]
        
至於能做什麼用途, 就自行發揮想像力吧
 

【PHP】LINE 回覆圖片訊息

參考資料 ----
Image message

 
<?php
// 因為網站是寄放在虛擬主機商那, 所以要指定時區
date_default_timezone_set('Asia/Taipei');
$channelAccessToken = '{從 LINE 官方帳號後台取得的 token}';
 
// 客戶端傳來的訊息
$bodyMsg = file_get_contents('php://input');

// 寫 log, 檔案在目前目錄下的 log 目錄內
// 檔名為 log_西元年月日.log, 一天一個檔
$sLogfile = './log/log_'.date('Ymd').'.log';
$fp = fopen($sLogfile, "a+");
fwrite($fp , print_r(date('Y-m-d H:i:s').', Recive: '.$bodyMsg."\n", true));
fclose($fp);

$obj = json_decode($bodyMsg, true);

foreach ($obj['events'] as &$event) 
{
   $userId = $event['source']['userId'];
   
    // 取得 使用者傳來的訊息
    $msgUser = $event['message']['text'];

    // 回傳 圖片訊息, 限 jpg 或 png
    $payload = [ 'replyToken' => $event['replyToken'],
                 'messages' => [ 
                                [
                                    "type" => "image",
                                    // 網址必須是 https, 網址字串長度不可超過 2000 字
                                    "originalContentUrl" => "https://完整網址/原始圖檔檔名.png", // 圖片不可超過 10M
                                    "previewImageUrl" => "https://完整網址/預覽圖檔檔名.png"      // 圖片不可超過 1M
                                ]
                            ]
            ];
            
    // Send reply API
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, 'https://api.line.me/v2/bot/message/reply');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [  'Content-Type: application/json',
                                            'Authorization: Bearer ' . $channelAccessToken
                                        ]);
    $result = curl_exec($ch);
    curl_close($ch);
}
 
?>
預覽圖



原始圖









【PHP】LINE 回覆貼圖訊息

參考資料 ----
List of available stickers

也可回覆貼圖, 只不過限於 LINE 官方的貼圖
 
<?php
// 因為網站是寄放在虛擬主機商那, 所以要指定時區
date_default_timezone_set('Asia/Taipei');
$channelAccessToken = '{從 LINE 官方帳號後台取得的 token}';
 
// 客戶端傳來的訊息
$bodyMsg = file_get_contents('php://input');

// 寫 log, 檔案在目前目錄下的 log 目錄內
// 檔名為 log_西元年月日.log, 一天一個檔
$sLogfile = './log/log_'.date('Ymd').'.log';
$fp = fopen($sLogfile, "a+");
fwrite($fp , print_r(date('Y-m-d H:i:s').', Recive: '.$bodyMsg."\n", true));
fclose($fp);

$obj = json_decode($bodyMsg, true);

foreach ($obj['events'] as &$event) 
{
   $userId = $event['source']['userId'];
   
    // 取得 使用者傳來的訊息
    $msgUser = $event['message']['text'];

    // 回傳貼圖訊息
    $payload = [ 'replyToken' => $event['replyToken'],
                'messages' => [ // 一次最多可回覆 5 張貼圖, 若超過 5 張, 程式就掛了, 不會有任何回應
                                [ 'type' => 'sticker',
                                  'packageId' => '446',
                                  'stickerId' => '1988'
                                ],
                                [ 'type' => 'sticker',
                                  'packageId' => '789',
                                  'stickerId' => '10855'
                                ],
                                [ 'type' => 'sticker',
                                  'packageId' => '8525',
                                  'stickerId' => '16581290'
                                ],
                                [ 'type' => 'sticker',
                                  'packageId' => '6136',
                                  'stickerId' => '10551376'
                                ],
                                [ 'type' => 'sticker',
                                  'packageId' => '6325',
                                  'stickerId' => '10979904'
                                ]
                            ]
            ];
            
    // Send reply API
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, 'https://api.line.me/v2/bot/message/reply');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [  'Content-Type: application/json',
                                            'Authorization: Bearer ' . $channelAccessToken
                                        ]);
    $result = curl_exec($ch);
    curl_close($ch);
}
 
?>






2022-06-30

【PHP】LINE 回覆文字訊息

參考資料 ----
List of available LINE emojis

文字訊息也可包含 emoji(顏文字), 只不過限於 LINE 官方的 emoji
 
<?php
// 因為網站是寄放在虛擬主機商那, 所以要指定時區
date_default_timezone_set('Asia/Taipei');
$channelAccessToken = '{從 LINE 官方帳號後台取得的 token}';
 
// 客戶端傳來的訊息
$bodyMsg = file_get_contents('php://input');

// 寫 log, 檔案在目前目錄下的 log 目錄內
// 檔名為 log_西元年月日.log, 一天一個檔
$sLogfile = './log/log_'.date('Ymd').'.log';
$fp = fopen($sLogfile, "a+");
fwrite($fp , print_r(date('Y-m-d H:i:s').', Recive: '.$bodyMsg."\n", true));
fclose($fp);

$obj = json_decode($bodyMsg, true);

foreach ($obj['events'] as &$event) 
{
   $userId = $event['source']['userId'];
   
    // 取得 使用者傳來的訊息
    $msgUser = $event['message']['text'];

    // 回覆含 emoji 的文字訊息
    // $ 的位置就是 emoji 的位置, 作用類似格式化輸出
    // 在本例, 2 個 $ 的位置分別為 0, 8
    $msgReply = '$含 emoji$ 的文字訊息';
    
    $payload = [ 'replyToken' => $event['replyToken'],
                'messages' => [  // 一次最多可回覆 5 則訊息, 若超過 5 則, 程式就掛了, 不會有任何回應
                                ['type' => 'text',
                                'text' => $msgReply,
                                'emojis' => [   [
                                                    "index"=> 0,    // emoji 在文字串的位置, 一定要和 $msgReply 中的 $ 的位置一致, 不然整個訊息都不會回覆
                                                    "productId" => "5ac1bfd5040ab15980c9b435",
                                                    "emojiId" => "001"
                                                ],
                                                [
                                                    "index" => 8,
                                                    "productId" => "5ac1bfd5040ab15980c9b435",
                                                    "emojiId" => "005"
                                                ]
                                        ]
                                ],
                                [   'type' => 'text',
                                    'text' => '文字訊息 2'
                                ],
                                [   'type' => 'text',
                                    'text' => '文字訊息 3'
                                ],
                                [   'type' => 'text',
                                    'text' => '文字訊息 4'
                                ],
                                [   'type' => 'text',
                                    'text' => '文字訊息 5'
                                ]
                            ]
            ];
            
    // Send reply API
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, 'https://api.line.me/v2/bot/message/reply');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [  'Content-Type: application/json',
                                            'Authorization: Bearer ' . $channelAccessToken
                                        ]);
    $result = curl_exec($ch);
    curl_close($ch);
}
 
?>






2022-06-25

【Python2】透過郵局 web service 查詢 3+3(6碼) 郵遞區號

參考資料 ----


郵局提供了 web service 方便 大量且快速查詢郵遞區號。

注意:要申請!要申請要申請
1. 開放 個人/團體/企業 申請,但要有固定 IP
2. 要填寫 web service 系統介接申請書,郵局才會將貴公司 IP 列入白名單,貴公司的查詢程式才能正常運作。
 
#!/usr/bin/python
#-*- coding:utf-8 -*-

import requests
import xml.etree.cElementTree as ET

...
...

def main():
    # sAddr = '高雄市前金區'
    # sAddr = '高雄市前金區光復一街'
    sAddr = '完整的地址'
    sZip = GetZip(sAddr)    # 正式
    # sZip = GetZip(sAddr,0)    # 測試
    if(sZip is None):
        print('查詢失敗')
    else:
        print('sZip = '+sZip)


# 郵局 web service, 取得 3+3 郵遞區號
# 傳入的參數 bTest: 執行函數的模式, 預設為 "正式"=1, 若為 0 時, 則列出傳回的各節點(node) 內容
def GetZip(ss, bTest=1):
    mydata = (''
                '<soap12:envelope xmlns:soap12="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
                    '<soap12:body>'
                        '<getzipcode xmlns="http://tempuri.org/">'
                            '<addrstr>' + ss + '</addrstr>'
                        '</getzipcode>'
                    '</soap12:body>'
                '</soap12:envelope>')
    url = 'http://33wsp.post.gov.tw/lzwzip/tzip33.asmx'
    headers = {'content-type': 'application/soap+xml'}
    response = requests.post(url,data=mydata,headers=headers)
    root = ET.fromstring(response.content)
    sResult = None
    for child in root.iter('*'):
        if(bTest==0):
            print(child.tag, child.text)
        if (child.tag=='{http://tempuri.org/}GetZipCodeResult'):
            if(child.text is None):
                pass
            else:
                if(len(child.text)==6):
                    sResult = child.text
            break
    
    return sResult
 

郵局傳回的內容如下:
 
# 當地址不完整時, 傳回的值為空值 None
('{http://www.w3.org/2003/05/soap-envelope}Envelope', None)
('{http://www.w3.org/2003/05/soap-envelope}Body', None)
('{http://tempuri.org/}GetZipCodeResponse', None)
('{http://tempuri.org/}address', None)


# 當地址完整時
('{http://www.w3.org/2003/05/soap-envelope}Envelope', None)
('{http://www.w3.org/2003/05/soap-envelope}Body', None)
('{http://tempuri.org/}GetZipCodeResponse', None)
('{http://tempuri.org/}GetZipCodeResult', '801002')    # 當有資料成功傳回時, 郵遞區號在這
 


經實驗後,當地址不完整時, web service 會傳回 None 或 3 碼郵遞區號
2022.06.25 測試,之前還會有傳回 3 碼郵區的情況,現在好像只會傳回 None 6 碼郵區

當地址內含有 路/街... 等,即使沒有門牌號碼,郵局 web service 就可能傳回 6 碼郵區,例如:"高雄市前金區光復一街",因此 並不能將 web service 做為判別地址完整性的方法。





2022-06-12

【Python】連線 MS SQL Server

參考資料 ----

微軟官方建議的驅動程式有 2 個 -- pyodbcpymssql,在本例採用的是 pymssql
 
#!/usr/bin/python
#-*- coding:utf-8 -*-

import  pymssql

...
...

def  main():
    conn = pymssql.connect(server='主機IP', user='帳戶名', password='密碼', database='資料庫名')
    cursor = conn.cursor(as_dict=True)    # 啟用以欄位名讀出欄位值
    
    # 查詢
    ssql = ("SELECT * FROM 資料表名 \n"
            "    WHERE 1=1 \n"
            "        AND 篩選條件1 \n"
            "        AND 篩選條件2 \n"
            "        AND ...")
    # SQL 語句以 括弧 包起,就可以寫多列
    # 每列最末是讓語句折行
    # 除錯時較易閱讀
    
    cursor.execute(ssql)
    
    # 方法一:
    # 一次只取出一筆記錄
    row = cursor.fetchone()
    while row:
        ff1 = row['欄位名']
        ...
        ...
        row = cursor.fetchone()
        
        
    # 方法二:
    # 一次取出所有記錄,並存入 list 中
    rows = cursor.fetchall()
    print("記錄數:"+str(cursor.rowcount))    # 一定要先 fetchall() 才能知道記錄數, 否則傳回值是 -1
    print("record count = "+str(len(rows)))    # 此法是讀出 list 有多少 element
    for rr in rows:
        ff1 = rr['欄位名']
        ...
        ...
        
        
    # 更新
    ssql = ("UPDATE 資料表名 \n"
            "    SET 欄位1='值1' \n"
            "      欄位2='值2' \n"
            "    WHERE 篩選條件")
    cursor.execute(ssql)
    conn.commit()    # 預設是沒有 auto commit 的
 


2022-05-28

【Linux Mint】20.3(Una) 安裝 AndroidStudio

參考資料 ----

系統需求:
* 64 位元作業系統
* 64 位元 CPU, 並開啟 虛擬機功能(要在電腦上跑 Android 模擬器 所需要)
* 8G 以上的 RAM
* 1280 x 800 以上解析度

先在根目錄建立 Android SDK 的安裝目錄(這是老人家的壞習慣, 但因為反正只有自己在用, 所以...小朋友不要學😜), 以我的電腦為例: /android/


再建立 AndroidStudio 的安裝目錄, /android-studio/


AndroidStudio 官網下載, 這是一個壓縮包, 解開後, 會是一個資料夾, 資料夾名稱就是 /android-studio/, 將資料夾內的檔案移至 /android-studio/ 內.


/android-studio/bin/ 下有個檔案 studio.sh,
在檔案上點滑鼠右鍵
→ 建立鏈結
→ 將生成的 鏈結(捷徑)檔 拖放到 桌面.
這樣就是建立捷徑.


雙擊桌面上的 AndroidStudio 捷徑
→ 點選 "在終端機中執行", 啟動 AndroidStudio
→ 第一次執行, 會看到 "No Android SDK found." 的警示訊息, 就依指示安裝到 /android/sdk/
→ 點選 AndroidStudio 啟動畫面右上角的(直立) 3 個點圖示
SDK Manager
SDK Tools 頁籤
→ 勾選 Android SDK Command-line Tools(開發 Flutter app 會用到)


修改使用者設定, 讓系統知道 Android SDK 安裝所在
 
[user]# vim  ~/.bashrc

# 在檔案最末加上這兩列
export ANDROID_HOME=/android/sdk
export PATH=$PATH:$ANDROID_HOME/tools

:x 存檔離開

重新登入 或 重開機 讓設定生效
 
wwwww

2022-05-23

【Linux Mint】20.3(Una) 安裝/設定 Flutter

要安裝 Flutter,必須先安裝 AndroidStudio(另有一說AndroidStudio 體積太大太笨重,VisualStudioCode 是另一種選擇)及 Google Chrome 瀏覽器(做為 debug 用),只要搜尋 "google chrome" 並下載安裝即可。

修改使用者設定, 讓系統知道 Android SDK 安裝所在
 
以我的電腦為例
Android 安裝在 /android (根目錄下)

[user]# vim  ~/.bashrc

# 在檔案最末加上這兩列
export ANDROID_HOME=/android/sdk
export PATH=$PATH:$ANDROID_HOME/tools

:x 存檔離開

重新登入 或 重開機 讓設定生效
 

安裝 Flutter 最快速簡便的方式就是透過 snapd 安裝
 
[user]# sudo  snap  install  flutter  --classic

檢查 Flutter 安裝在哪裡
[user]# flutter  sdk-path
以我的電腦為例, 安裝在 /home/使用者名稱/snap/flutter/common/flutter


檢查 Flutter 安裝的完整度
[user]# flutter  doctor
看到 驚嘆號的都要設法滿足需求, 讓驚嘆號消失


同意/接受 授權聲明
[user]# flutter  doctor  --android-licenses

切換 Flutter 到 stable 頻道
[user]# flutter  channel stable

更新 Flutter 到最新版
[user]# flutter  upgrade

目前(2022.05.28) Flutter 已到 3.0, 支援更多平台: Android, iOS, Web, Windows, Linux, MacOS


設定可開發 Linux 桌面程式(圖形界面程式)
[user]# flutter  config  --enable-linux-desktop
 
可以開始開發 Flutter app 了

啟動 AndroidStudio
→ "New Flutter Project"
依指示操作
預設 Flutter 會自動幫您建立基本的框架程式, 包含各個平台(Android, iOS, web, ...)

您也可以在終端機視窗以命令列指令新建您的專案
 
切換到您的專案管理目錄
[user]# cd /case/flutter

新建專案
[user]# flutter  create  專案名稱
 
個人覺得以 命令方式新建專案較方便, Flutter 會自動幫您產生各平台的程式碼, 如果是以 AndroidStudio 新建專案, 則 AndroidStudio 只會產生您執行 AndroidStudio 的作業系統的程式碼, 例如: 我現在是 Linux 系統, 在點擊 "New Flutter Project" 後, AndroidStudio 就沒勾選 MacOS 和 Windows, 而且也沒法勾選.

看這氣勢...是不是想幹掉 Qt 啊 😈

【Linux Mint】20.3(Una) 安裝 snapd

snapd 似乎是類似 應用程式市集,雖然同屬 Ubuntu 家族,但是 LinuxMint20.3(Una) 是沒有預先安裝的。

移除 /etc/apt/preferences.d/nosnap.pref,(看這檔名,似乎 LinuxMint 壓根就沒打算讓使用者裝 snapd)保險起見,可移至其他目錄保留。
 
[user]#  sudo  apt  update

[user]#  sudo apt  install  snapd
 

重新登入 或 重開機 後 就生效了