# 水利會資訊人員教育訓練LineBot課程
## 課前準備:
1.Microsoft Account(請務必於上課前申請)
https://account.microsoft.com/account?lang=zh-TW
2.Azure Portal Account(正式上線)
https://azure.microsoft.com/zh-tw/free/
或
https://azure.microsoft.com/zh-tw/free/students/
3.LINE Account (設計Chat Bot用,請務必於上課前申請)
https://developers.line.biz/console/
4.手機(收取簡訊驗證身分用)
5.信用卡(如果無法於上課前申請完成Azure Account則可能需要)
## 上課環境:
1.Windows 10中文版
2.Visual Studio Code
https://code.visualstudio.com/
3.Visual Studio 2017/2019 Community 以上版本 (windows版)
https://visualstudio.microsoft.com/zh-hant/vs/community/
4.Postman
https://www.getpostman.com/downloads/
5.Ngrok
https://ngrok.com/download
6.建議使用 Chrome 瀏覽器
LINE Bot部分
---
**LINE** 官方帳號2.0
http://at-blog.line.me/tw/archives/lineat_manual_upgrade_01.html
http://at-blog.line.me/tw/archives/LINEOA2.0.html
### 00.建立LINE Bot
1.使用個人LINE身分登入Line Developer站台
https://developers.line.biz
https://manager.line.biz/
2.建立Provider
3.建立Channel -->> 選擇Messaging API
4.請以自己的LINE加入該Bot為好友
目標:
1.取得channel access token
2.取得User Id
### 使用Nuget Package 引用LineBotSDK
說明(建立Bot物件)
```csharp=
using isRock.LINE;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
namespace isRock.LineBot
{
/// <summary>
/// 操作Line bot的類別實體
/// </summary>
public class Bot
{
private const string LineSendMessageEndpoint = "https://api.line.me/v2/bot/message/push";
private const string LineReplyMessageEndpoint = "https://api.line.me/v2/bot/message/reply";
private string channelAccessToken
{
get;
set;
}
/// <summary>
/// 建構函式
/// </summary>
/// <param name="ChannelAccessToken">請填入很長的ChannelAccessToken</param>
public Bot(string ChannelAccessToken)
{
this.channelAccessToken = ChannelAccessToken;
}
/// <summary>
/// 建構函式(自動抓取appSetting中的ChannelAccessToken)
/// </summary>
public Bot()
{
bool flag = !ConfigurationManager.AppSettings.AllKeys.Contains("ChannelAccessToken");
if (flag)
{
throw new Exception("Please specify 'Channel Access Token' in the appSettings or constructor parameters.");
}
this.channelAccessToken = ConfigurationManager.AppSettings["ChannelAccessToken"];
}
..........................................>>>>>
```
說明(建立Bot HttpPost方法)
```csharp=
using Newtonsoft.Json;
using System;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Text;
namespace isRock.LINE
{
internal class Utility
{
/// <summary>
/// Line使用的標準 http post action ,回傳 JSON string
/// </summary>
/// <param name="JSON">傳入參數</param>
/// <param name="Endpoint">Endpoint</param>
/// <param name="ChannelAccessToken">ChannelAccessToken</param>
/// <returns></returns>
internal static string LineHttpPost(string JSON, Uri Endpoint, string ChannelAccessToken)
{
string @string;
try
{
WebClient webClient = new WebClient();
webClient.Headers.Clear();
webClient.Headers.Add("Content-Type", "application/json");
webClient.Headers.Add("Authorization", "Bearer " + ChannelAccessToken);
byte[] bytes = Encoding.UTF8.GetBytes(JSON);
byte[] bytes2 = webClient.UploadData(Endpoint, bytes);
@string = Encoding.UTF8.GetString(bytes2);
}
catch (WebException ex)
{
string webException = Utility.GetWebException(ex);
throw new Exception(string.Format("\n LineHttpPost Exception:{0} \nResponse:{1} \nEndpoint:{2} \nJSON:{3}", new object[]
{
ex.Message,
webException,
Endpoint,
JSON
}), ex);
}
return @string;
}
..........................................>>>>>
```
### 01.讓LINE Bot發送訊息(文字、貼圖、圖片)
1.建立.net fx 4.6.1以上專案(WebForm, WebAPI, MVC任何形式均可,建議選用Web/Empty project,勾選WebAPI, WebForm)
2.透過Push API發送文字、貼圖、圖片訊息
目標:
可成功發送訊息給自己
note:
1.貼圖可參考位置??
https://devdocs.line.me/files/sticker_list.pdf
2.圖片訊息url必須是??
question:
如果需要發給其他人?
### 使用TempalteMessage
範例
```csharp=
protected void Button1_Click(object sender, EventArgs e)
{
var bot = new isRock.LineBot.Bot(channelAccessToken);
var ButtonTmpMsg = new isRock.LineBot.ButtonsTemplate()
{
text = "文字",
title = "標題",
altText = "替代文字",
thumbnailImageUrl = new Uri("https://66.media.tumblr.com/2e37eafc9b6b715ed99b31fb6f72e6a5/tumblr_inline_pjjzfnFy7a1u06gc8_640.jpg")
};
//add actions
var action1 = new isRock.LineBot.MessageAction()
{ label = "顯示的標題", text = "呈現的文字" };
ButtonTmpMsg.actions.Add(action1);
var action2 = new isRock.LineBot.UriAction()
{ label = "顯示的標題", uri = new Uri("http://www.google.com") };
ButtonTmpMsg.actions.Add(action2);
bot.PushMessage(AdminUserId, ButtonTmpMsg);
}
```
### 使用QuickReply
1.在文字或template訊息後面跟著QuickReply
2.QuickReplyItem支援的Actions
* 1.datetime picker
* 2.postback
* 3.GPS
* 4.Text
* 5.icon
question:
1.QuickReply最多可以幾個?
範例程式碼:
```csharp=
protected void Button4_Click(object sender, EventArgs e)
{
//icon位置
const string IconUrl = "https://arock.blob.core.windows.net/blogdata201809/1337594581_package_edutainment.png";
//建立一個TextMessage物件
isRock.LineBot.TextMessage m =
new isRock.LineBot.TextMessage("請在底下選擇一個選項");
//在TextMessage物件的quickReply屬性中加入items
m.quickReply.items.Add(
new isRock.LineBot.QuickReplyMessageAction(
$"一般標籤", "點選後顯示的text文字"));
m.quickReply.items.Add(
new isRock.LineBot.QuickReplyMessageAction(
$"有圖示的標籤", "點選後顯示的text文字", new Uri(IconUrl)));
//加入QuickReplyDatetimePickerAction
m.quickReply.items.Add(
new isRock.LineBot.QuickReplyDatetimePickerAction(
"選時間", "選時間", isRock.LineBot.DatetimePickerModes.datetime,
new Uri(IconUrl)));
//加入QuickReplyLocationAction
m.quickReply.items.Add(
new isRock.LineBot.QuickReplyLocationAction(
"選地點", new Uri(IconUrl)));
//加入QuickReplyCameraAction
m.quickReply.items.Add(
new isRock.LineBot.QuickReplyCameraAction(
"Show Camera", new Uri(IconUrl)));
//加入QuickReplyCamerarollAction
m.quickReply.items.Add(
new isRock.LineBot.QuickReplyCamerarollAction(
"Show Cameraroll", new Uri(IconUrl)));
//建立bot instance
isRock.LineBot.Bot bot = new isRock.LineBot.Bot(channelAccessToken);
//透過Push發送訊息
bot.PushMessage(AdminUserId, m);
}
```
### 一次發送多則訊息
```csharp=
protected void Button3_Click(object sender, EventArgs e)
{
//建立Bot instance
isRock.LineBot.Bot bot =
new isRock.LineBot.Bot(channelAccessToken); //傳入Channel access token
var Msgs = new List<isRock.LineBot.MessageBase>();
Msgs.Add(new isRock.LineBot.TextMessage("文字"));
Msgs.Add(new isRock.LineBot.StickerMessage(1, 2));
//bot.PushMessage(AdminUserId, 1,2);
bot.PushMessage(AdminUserId, Msgs);
}
```
### 一次發送多人訊息
1.建立mem物件
```csharp=
namespace LineBot_1.member
{
public class mem
{
public int memID { get; set; }
public int lineBotID { get; set; }
public string lineUserID { get; set; }
public string displayName { get; set; }
public string groupName { get; set; }
}
}
```
2.建立資料庫連線相關物件(DataAcess)
3.建立資料存取相關物件(DAOs)
4.發送多人訊息 foreach(var _mem in mems){}
### 建立WebHook
建立WebHook所需的WebAPI,接收文字、貼圖、或其他訊息,並回覆文字與貼圖
1.建立新專案,記得勾選WebAPI 選項
2.注意Controller
```csharp=
public class TiaController : ApiController
{
[Route("api/TiaItController")] //[Attribute]...特性與屬性??
[HttpPost]
public IHttpActionResult POST()
{...............
```
3.取得用戶的UserId, 發送來的訊息,並echo回覆
note:
1.reply token的有效期限?10餘秒
2.reply token的使用次數?1次
:::info
{"events":[{"type":"message","replyToken":"0e5b5722b22f4dbb94e220f63a9bb186","source":{"userId":"U459976e042f3e5883a325a09ff6a447b","type":"user"},"timestamp":1565502497776,"message":{"type":"text","id":"10372067470071","text":"Aaq"}}],"destination":"U01213c4d6a6418f8d74e0e71d4e75281"}
*******************************************************************************************************
{"events":[{"type":"message","replyToken":"7153af77f3514ec6b07ddf9820b88b50","source":{"userId":"U459976e042f3e5883a325a09ff6a447b","type":"user"},"timestamp":1565848501824,"message":
{"type":"location","id":"10395009607156","address":"330台灣桃園市桃園區守法路76巷7號","latitude":24.993939,"longitude":121.297372}}],"destination":"U01213c4d6a6418f8d74e0e71d4e75281"}
*******************************************************************************************************
{"events":[{"type":"message","replyToken":"ba1ddbb84e49458d8b3cab21fcb64b7e","source":{"groupId":"C0107eefc65ba67d41d072b85dab14974","userId":"U459976e042f3e5883a325a09ff6a447b","type":"group"},"timestamp":1566109265233,"message":{"type":"text","id":"10412445897634","text":"Qq"}}],"destination":"U01213c4d6a6418f8d74e0e71d4e75281"}
*******************************************************************************************************
{"events":[{"type":"join","replyToken":"4f7f6caa895549e8b2db2b3ddb0bd224","source":{"groupId":"C54b10824f9fdfae6c81debfdae4d2ef1","type":"group"},"timestamp":1566111053851}],"destination":"U01213c4d6a6418f8d74e0e71d4e75281"}
*******************************************************************************************************
{"events":[{"type":"message","replyToken":"e50196c817ce43b687457efc4ecfca56","source":{"userId":"U459976e042f3e5883a325a09ff6a447b","type":"user"},"timestamp":1567130465944,"message":{"type":"image","id":"10480140561571","contentProvider":{"type":"line"}}}],"destination":"U01213c4d6a6418f8d74e0e71d4e75281"}
:::
question:
1.如何區分收到的訊息類型?
ex.type,replytoken,message,postback.....
2.如何找到傳訊者userid?
3.為何events是一個集合物件?
4.一次要回三則訊息怎麼辦?
5.Group,Room與LineBot關係,透過GroupID發送...
#### 如何使用ngrok
https://studyhost.blogspot.com/2018/09/clinebot26-ngrokline-botlocalhostwebhook.html
### 取得用戶用戶的資訊,並顯示
```csharp=
(...略...)
if (LineEvent.message.type == "text") //收到文字
{
var user = this.GetUserInfo(LineEvent.source.userId);
this.ReplyMessage(LineEvent.replyToken,
$"Hi, {user.displayName} 你({user.userId})說了:\n" + LineEvent.message.text);
}
(...略...)
```
1.this??
```csharp=
public class UploadimgController : isRock.LineBot.LineWebHookControllerBase
```
### 取得用戶上傳的圖片,並儲存在網站上
*Nuget安裝Imgur.Api
```csharp=
(...略...)
if (LineEvent.message.type == "image")
{
string path = System.Web.HttpContext.Current.Request.MapPath("/temp/");
var filename = Guid.NewGuid().ToString() + ".png";
var fileBody = this.GetUserUploadedContent(LineEvent.message.id);
System.IO.File.WriteAllBytes(path + filename, fileBody);
var fileURL = $"http://{System.Web.HttpContext.Current.Request.Url.Host}/temp/{filename}";
this.ReplyMessage(LineEvent.replyToken,
$"收到一個圖檔,位於:\n {fileURL}");
}
(...略...)
```
### 設計 Rich Menu
2種方式...(顯示順序)
![](https://i.imgur.com/uZ7L4uB.jpg)
Line official Account Manager 後台:
https://manager.line.biz/
參考範例(動態切換rich menu上, 下一頁):
參考圖片(可以用ppt設計)2500x1686 or 2500x843 pixels
:::info
### Exercise 動態建立Rich Menu
Item-->Area
1.Button 1 --> 撰寫程式馬動態建立 1 or 2個 Rich Menu
```
protected void Button1_Click(object sender, EventArgs e)
{
//建立RuchMenu
var item = new isRock.LineBot.RichMenu.RichMenuItem();
item.name = "no name";
item.chatBarText = "快捷選單Tia";
//建立左方按鈕區塊
var leftButton = new isRock.LineBot.RichMenu.Area();
leftButton.bounds.x = 0;
leftButton.bounds.y = 0;
leftButton.bounds.width = 460;
leftButton.bounds.height = 1686;
leftButton.action = new MessageAction() { label = "左", text = "/左" };
//建立右方按鈕區塊
var rightButton = new isRock.LineBot.RichMenu.Area();
rightButton.bounds.x = 2040;
rightButton.bounds.y = 0;
rightButton.bounds.width = 2040 + 460;
rightButton.bounds.height = 1686;
rightButton.action = new MessageAction() { label = "右", text = "/右" };
//將area加入RichMenuItem
item.areas.Add(leftButton);
item.areas.Add(rightButton);
//建立Menu Item並綁定指定的圖片
var menu = isRock.LineBot.Utility.CreateRichMenu(
item, new Uri("http://arock.blob.core.windows.net/blogdata201902/test01.png"), channelAccessToken);
//RichMenu建立完成,會回傳richMenuIdResponse 中richMenuId
//將Menu Item設為預設Menu
isRock.LineBot.Utility.SetDefaultRichMenu(menu.richMenuId, channelAccessToken);
Response.Write($"OK, {menu.richMenuId}");
}
```
3.透過API將Rich Menu綁定到特定用戶身上--顯示時機??
:::
### 建立Liff
1.從後台建立Liff
2.使用RWD頁面
可使用: https://testliff.azurewebsites.net/default.html
3.取得Liff URL
4.貼給朋友試試看
:::info
### Exercise 建立綁定在Rich Menu上的Liff
1.將建立好的Liff URL,透過UriAction,配置到動態建立的Rih Menu Area中
:::
### 如何一次發送多則訊息(包含 template)
```csharp=
protected void Button3_Click(object sender, EventArgs e)
{
DAOs Linemems = new DAOs();
List<mem> Mems = new List<mem>();
Mems = Linemems.Query();
int p_count = 1;
foreach (var _mem in Mems)
{
string mUid = _mem.lineUserID;
isRock.LineBot.Bot bot = new isRock.LineBot.Bot(token);
bot.PushMessage(mUid, "TIA多人測試訊息-->" + p_count.ToString());
p_count+=1;
}
}
```
2019/10/9
===
LINE Login
:::info
Exercise
練習範例
1.申請LINE Login
2.取得id. secret
3.更新LINE後台Callback url
:::
### OAUTH之code與token
```csharp=
(...略...)
public static GetTokenFromCodeResult GetTokenFromCode(string code, string ClientId, string ClientSecret, string redirect_uri)
{
GetTokenFromCodeResult result;
try
{
WebClient webClient = new WebClient();
webClient.Encoding = Encoding.UTF8;
webClient.Headers.Clear();
NameValueCollection nameValueCollection = new NameValueCollection();
nameValueCollection["grant_type"] = "authorization_code";
nameValueCollection["code"] = code;
nameValueCollection["redirect_uri"] = redirect_uri;
nameValueCollection["client_id"] = ClientId;
nameValueCollection["client_secret"] = ClientSecret;
byte[] bytes = webClient.UploadValues("https://api.line.me/oauth2/v2.1/token", nameValueCollection);
string @string = Encoding.UTF8.GetString(bytes);
GetTokenFromCodeResult getTokenFromCodeResult = JsonConvert.DeserializeObject<GetTokenFromCodeResult>(@string);
result = getTokenFromCodeResult;
}
catch (WebException ex)
{
using (StreamReader streamReader = new StreamReader(ex.Response.GetResponseStream()))
{
string str = streamReader.ReadToEnd();
throw new Exception("GetToeknFromCode: " + str, ex);
}
}
return result;
}
(...略...)
```
參考資料(Rest API)
https://developers.line.biz/en/reference/social-api/
Mail-->tia.a109@gmail.com
===