開啟LINE Notify服務
平常我們都會用LINE傳訊息,不僅免費還有很多可愛貼圖,三五好友還可以組成群組,讓分散在各地的親朋好友聊天打鬧,真的很方便。不過 Kotlin App 是要如何透過LINE來傳訊息呢?
LINE有提供一個官方的工具,可以讓我們透過該工具傳遞訊息,不過傳遞之前必須先取得傳訊對象的密碼,密碼也稱為權杖,當網站收到這些密碼及訊息後,就會將訊息轉送到指定的LINE群組。也就是說,ESP32並不是直接把訊息傳到某個人手機的LINE裡面,而是透過LINE官方工具協助中間轉傳訊息。
通常這類的網站就通稱為API(Application Programming Interface),使用這些API工具我們就可以省去學習LINE APP內複雜的通訊協定、機制、格式、架構…等等,只需要把資料傳給API,API就會處理所有的通訊過程,讓我們省下很多程式開發的時間。
整理上面的內容,也就是說首先我們利用 Python or Kotlin App 監控目標,當發現異常時(例如溫度>40或濕度>85)就發送到LINE的API網站,LINE API收到我們的訊息之後,就會轉傳到指定的LINE群組中,讓相關的管理人員收到訊息,然後立即處理。
要完成以上的任務,本節分成幾個步驟:
1. 申請LINE Notify權杖
2. 手動測試LINE Notify
3. 程式設計
1. 申請LINE Notify權杖
首先我們先到LINE Notify的網站申請開通服務,首先在瀏覽器中輸入網址:https://notify-bot.line.me/,並點選右上角的登入。
輸入自己的LINE帳號密碼後,按下方的登入。
完成登入,點選右上角的登入帳號/個人頁面,即可進入「已連動服務」管理頁面。
在頁面下方,點選發行權杖按鈕,權杖原意為通行碼,可視為與特定LINE群組通訊的密碼,若無此密碼,我們的LINE可能每天都會收到一堆廣告訊息。
在設定權杖頁面分別輸入:
a.名稱:未來發訊訊息通知時,會出現的名稱
b.對象:要發送訊息的群組,此處練習時先選擇「透過1對1聊天接收LINE Notify通知」,也就是傳訊給自己,
另外這裡你會發現在清單中找不到你某個特定的朋友,而只能選「群組」來發送訊息,這是因為我們目前使用的LINE通知是免費的Notify功能,若您要通知特定的「人」則必須申請LINE Bot,這部份則較為複雜,本文先略過。
完成輸入後,按下方的發行即可獲得一組密碼,此即為權杖。
拿到權杖後,點選下方的複製按鈕將密碼複製起來,並貼在記事本上,避免遺失,若遺失也不用緊張,重新申請一次即可,不過就算申請的對象是同一個,每次的權杖密碼都是不相同的,但功能是一樣的,後面申請的也不會使之前申請的權杖失效。
確認申請完成,將會產生一個連動服務。
(2) 手動測試LINE Notify
完成申請權杖後,我們就先用手動測試剛剛申請的LINE Notify權杖是否正常,也順便了解LINE API的運作方式,如需要更詳細的LINE Notify API可參閱網址:https://notify-bot.line.me/doc/。
首先我們先到連線到知名的API測試網址API Tester:https://apitester.com/(或者安裝POSTMAN API測試軟體),並依照規定輸入LINE Notify的相關設定如下:
a.修改傳遞方式POST
b.輸入LINE Notify API網站:https://notify-api.line.me/api/notify
c.輸入傳遞的訊息內容:message=這是測試
d.點選「+」號,增加一個表頭Request Header,我們會將密碼放在表頭與LINE API做驗證:
- 輸入表頭名稱:Authorization
- 輸入認證內容:Bearer 權杖密碼,要注意的是Bearer與權杖密碼中間有一個空白。
完成設定後後,按下下方的藍色Test按鈕,即可在手機收到LINE傳來的訊息。
下圖中前面【】內的名稱就是一開始申請時的名稱(倉庫監看機器人),而後方的訊息「這是測試」則是在API tester網站輸入的內容。
經由上面的測試可以發現,用 Python or Android App 傳LINE的方式就是將資料傳遞到LINE的API網站https://notify-api.line.me/api/notify,並將訊息內容及權杖密碼以參數方式夾帶給網站,網站收到後就會將訊息傳遞給權杖所對應的使用者。
另外如果您選擇傳訊的對象是一個群組,那麼要多一個動作:邀請LINE Notify進入群組內,否則會出現一個錯誤訊息是:此帳號尚未被邀請至已連動的群組。
(3) Android Apps with Kotlin
MainActivity.kt
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
//reserved. unused.
import android.util.Log
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.readValue
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import org.json.JSONArray
val LINE_01 = "Bearer ml5FRlxPULD2DaBN0qwoCEWlg2TbZEXEjqX2locZevV"
val LINE_02 = "Bearer gUfFl3M7dZ5BY5BtOAQqGnviukii1LOUbvMoUpbX9Us"
fun sendLineMsg(msg : String, lineID : String=LINE_02) {
val client = OkHttpClient.Builder()
.build()
val payload = "message=" + msg
val requestBody = payload.toRequestBody()
val request = Request.Builder()
.method("POST", requestBody)
.url("https://notify-api.line.me/api/notify")
.header("User-Agent", "Mozilla/5.0 (compatible;")
.addHeader("Authorization", lineID)
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.build()
CoroutineScope(Dispatchers.IO).launch {
var response = client.newCall(request).execute()
response.body?.run {
val json = string()
}
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sendLineMsg("LINE 訊息測試")
}
}
Build.gradle
dependencies {
def room_version = "2.2.5"
def work_version = "2.4.0"
implementation "androidx.work:work-runtime-ktx:$work_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.gms:play-services-maps:17.0.0'
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.11.+"
implementation 'com.google.code.gson:gson:2.8.6'
implementation("com.squareup.okhttp3:okhttp:4.9.0")
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2'
//implementation "androidx.room:room-runtime:$room_version"
//kapt "androidx.room:room-compiler:$room_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}