Try   HackMD

水利會資訊人員教育訓練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物件)

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方法)

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

範例

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最多可以幾個?

範例程式碼:

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); }

一次發送多則訊息

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物件

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

public class TiaController : ApiController { [Route("api/TiaItController")] //[Attribute]...特性與屬性?? [HttpPost] public IHttpActionResult POST() {...............

3.取得用戶的UserId, 發送來的訊息,並echo回覆

note:
1.reply token的有效期限?10餘秒
2.reply token的使用次數?1次

{"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

取得用戶用戶的資訊,並顯示

(......) 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??

public class UploadimgController : isRock.LineBot.LineWebHookControllerBase

取得用戶上傳的圖片,並儲存在網站上

*Nuget安裝Imgur.Api

(......) 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種方式(顯示順序)

Line official Account Manager 後台:
https://manager.line.biz/
參考範例(動態切換rich menu上, 下一頁):

參考圖片(可以用ppt設計)2500x1686 or 2500x843 pixels

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.貼給朋友試試看

Exercise 建立綁定在Rich Menu上的Liff

1.將建立好的Liff URL,透過UriAction,配置到動態建立的Rih Menu Area中

如何一次發送多則訊息(包含 template)

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

Exercise
練習範例
1.申請LINE Login
2.取得id. secret
3.更新LINE後台Callback url

OAUTH之code與token

(......) 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