# 水利會資訊人員教育訓練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 ===