Line Bot連續對話
一個連續對話的例子,像是「請假」。
每道問題的構成
一道問題包含「Order(提問順序)
」、「XXXQuestion(問句)
」、「Answer(預期答案的過濾法則, 答非所問的抱怨)
」。
using OpenLineBot.Service;
namespace OpenLineBot.Models.Conversation.Entity.Custom
{
public class LeaveApply : ConversationEntity
{
public LeaveApply(BotService bot) : base (bot) { }
[Order(1)]
[TextQuestion("給我員工編號?")]
[Answer(typeof(EmployIdFilter), "是4個數字好嗎!")]
public string EmployId { get; set; }
[Order(2)]
[TextPickerQuestion("請哪種假?", new string[] {"公假", "事假", "病假"})]
[Answer(typeof(LeaveCateFilter), "用選的,不要自己亂回!")]
public string LeaveCate { get; set; }
[Order(3)]
[DateTimePickerQuestion("何時開始?")]
[Answer(typeof(LeaveStartFilter), "用選的,不要自己亂回!")]
public string LeaveStart { get; set; }
[Order(4)]
[TextQuestion("請幾天?")]
[Answer(typeof(LeaveDaysFilter), "給個數目好嗎!")]
public string LeaveDays { get; set; }
[Order(5)]
[TextQuestion("請幾小時?")]
[Answer(typeof(LeaveHoursdFilter), "0到8小時!")]
public string LeaveHours { get; set; }
[Order(6)]
[ConfirmQuestion("要提交了嗎?")]
[Answer(typeof(SubmitFilter), "用選的,不要自己亂回!")]
public string Submit { get; set; }
}
}
機器人在想什麼?
運作的核心說穿就是if-else分支樹(我用Chain of Responsibility Pattern包裝,如果任何人有其他建議,請在GitHub發Issue或是寄信讓我知道…)。
using OpenLineBot.Models.Conversation.Entity.Custom;
using OpenLineBot.Models.Conversation.Handler.Custom;
using OpenLineBot.Service;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Http;
namespace OpenLineBot.Controllers
{
public class DefaultController : ApiController
{
[HttpPost]
public IHttpActionResult POST()
{
BotService bot = null;
try
{
string postData = Request.Content.ReadAsStringAsync().Result;
var receivedMessage = isRock.LineBot.Utility.Parsing(postData);
var evt = receivedMessage.events.FirstOrDefault();
SecretInfo secret = Load();
bot = new BotService(secret.ChannelAccessToken, secret.AdminId, evt);
var handlerSubmit = new SubmitHandler<LeaveApply>(bot);
var handlerByeBye = new ByeByeHandler<LeaveApply>(bot);
var handlerNoKeyWord = new NoKeyWordHandler<LeaveApply>(bot);
var handlerComplaint = new ComplaintHandler<LeaveApply>(bot);
var handlerFirstQuestion = new FirstQuestionHandler<LeaveApply>(bot);
var handlerNextQuestion = new NextQuestionHandler<LeaveApply>(bot);
var handlerFinalQuestion = new FinalQuestionHandler<LeaveApply>(bot);
var handlerNotFinalQuestion = new NotFinalQuestionHandler<LeaveApply>(bot);
var handlerRecorded = new RecordedHandler<LeaveApply>(bot);
var handlerNotRecorded = new NotRecordedHandler<LeaveApply>(bot);
var handlerNotBye = new NotByeHandler<LeaveApply>(bot);
var handlerText = new TextHandler<LeaveApply>(bot);
var handlerLineEvent = new LineEventHandler<LeaveApply>(bot);
// Set seccessors
handlerSubmit.SetSeccessor(handlerSubmit.SuccessorDic["ByeBye"], handlerByeBye);
handlerFinalQuestion.SetSeccessor(handlerFinalQuestion.SuccessorDic["Submit"], handlerSubmit);
handlerFinalQuestion.SetSeccessor(handlerFinalQuestion.SuccessorDic["ByeBye"], handlerByeBye);
handlerNotFinalQuestion.SetSeccessor(handlerNotFinalQuestion.SuccessorDic["NextQuestion"], handlerNextQuestion);
handlerNotFinalQuestion.SetSeccessor(handlerNotFinalQuestion.SuccessorDic["Complaint"], handlerComplaint);
handlerRecorded.SetSeccessor(handlerRecorded.SuccessorDic["FinalQuestion"], handlerFinalQuestion);
handlerRecorded.SetSeccessor(handlerRecorded.SuccessorDic["NotFinalQuestion"], handlerNotFinalQuestion);
handlerNotRecorded.SetSeccessor(handlerNotRecorded.SuccessorDic["FirstQuestion"], handlerFirstQuestion);
handlerNotRecorded.SetSeccessor(handlerNotRecorded.SuccessorDic["NoKeyWord"], handlerNoKeyWord);
handlerNotBye.SetSeccessor(handlerNotBye.SuccessorDic["Recorded"], handlerRecorded);
handlerNotBye.SetSeccessor(handlerNotBye.SuccessorDic["NotRecorded"], handlerNotRecorded);
handlerText.SetSeccessor(handlerText.SuccessorDic["ByeBye"], handlerByeBye);
handlerText.SetSeccessor(handlerText.SuccessorDic["NotBye"], handlerNotBye);
handlerLineEvent.SetSeccessor(handlerLineEvent.SuccessorDic["Text"], handlerText);
handlerLineEvent.HandleRequest();
return Ok();
}
catch (Exception ex)
{
bot.Notify(ex);
return Ok();
}
}
SecretInfo Load()
{
string tokenPath = HttpContext.Current.Server.MapPath(@"../App_Data/secret.token");
using (StreamReader r = new StreamReader(tokenPath))
{
string json = r.ReadToEnd();
return JsonConvert.DeserializeObject<SecretInfo>(json);
}
}
}
internal class SecretInfo
{
public string AdminId { get; set; }
public string ChannelAccessToken { get; set; }
}
}
如何使用?
- 務必新增資料庫檔案
請預先單獨執行DbStarter/Programs.cs以生成OpenLineBot/App_Data/LineBotDb.sqlite - 務必在App_Data目錄新增檔案secret.token
{ "AdminId":"YourLineUserID", "ChannelAccessToken":"YourChannelAccessToken" }
- 如欲變更問題的順序、問句、抱怨
請實作一個如LeaveApply
能繼承ConversationEntity
的類別。 - 如欲設計問題的過濾法則
請參考ConcreteFilters.cs裡面的類別,實作IFilter
。 - 如欲擴充機器人的提問模板(點選時間日期或是純文字提問,就是採用不同模板)
請參考Questions.cs裡面的類別,實作IQuestion
,並且ConversationEntity
的方法PushQuestion也得跟著增加case。 - 如欲改變機器人的分支流程
請參考ConcreteHandlers.cs裡面的類別,以XXXHandler
命名,實作Handler<T>
,留意T
會實作IConversationEntity
;若XXXHandler
有下個分支YYYHandler
,請務必做SuccessorDic.Add(“YYY”, 編號)。最後在DefaultController
串接好每個實例Handler的Successor,並呼叫第一個實例Handler的方法HandleRequest。
程式碼放在哪裡?
沒做完的部份
- 要保留多長的連續對話時間?
- 機器人沒有辨別用戶是否為公司員工?