Simplified conversation logic

This commit is contained in:
Eugene Zadyra
2018-07-12 12:10:55 +02:00
parent 341d54f3df
commit 951ce55725
16 changed files with 88 additions and 108 deletions

View File

@@ -38,12 +38,12 @@ class Account(val userId: UserId, bot: Bot, dataService: DataService, val locali
entryPoint(askAction) entryPoint(askAction)
def askAction: Step = def askAction: Step =
question { _ => ask { _ =>
val credentials = dataService.getUserCredentials(userId.userId) val credentials = dataService.getUserCredentials(userId.userId)
val currentAccount = credentials.find(c => c.accountId == userId.accountId).getOrElse(sys.error("Can't determine current account")) val currentAccount = credentials.find(c => c.accountId == userId.accountId).getOrElse(sys.error("Can't determine current account"))
val buttons = Seq(Button(lang.addAccount, -1L), Button(lang.deleteAccount, -2L)) ++ credentials.map(c => Button(s"🔐️ ${c.username}", c.accountId)) val buttons = Seq(Button(lang.addAccount, -1L), Button(lang.deleteAccount, -2L)) ++ credentials.map(c => Button(s"🔐️ ${c.username}", c.accountId))
bot.sendMessage(userId.source, lang.pleaseChooseAccount(currentAccount.username), inlineKeyboard = createInlineKeyboard(buttons, columns = 1)) bot.sendMessage(userId.source, lang.pleaseChooseAccount(currentAccount.username), inlineKeyboard = createInlineKeyboard(buttons, columns = 1))
} answer { } onReply {
case Msg(cmd@CallbackCommand(actionStr), _) => case Msg(cmd@CallbackCommand(actionStr), _) =>
val action = actionStr.toLong val action = actionStr.toLong
action match { action match {

View File

@@ -84,12 +84,12 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
}(requestNext = requestDateFrom) }(requestNext = requestDateFrom)
private def requestDateFrom: Step = private def requestDateFrom: Step =
question { bookingData => ask { bookingData =>
datePicker ! InitConversation datePicker ! InitConversation
datePicker ! StartConversation datePicker ! StartConversation
datePicker ! DateFromMode datePicker ! DateFromMode
datePicker ! bookingData.dateFrom datePicker ! bookingData.dateFrom
} answer { } onReply {
case Msg(cmd: Command, _) => case Msg(cmd: Command, _) =>
datePicker ! cmd datePicker ! cmd
stay() stay()
@@ -98,37 +98,28 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
} }
private def requestDateTo: Step = private def requestDateTo: Step =
question { bookingData => ask { bookingData =>
datePicker ! InitConversation datePicker ! InitConversation
datePicker ! StartConversation datePicker ! StartConversation
datePicker ! DateToMode datePicker ! DateToMode
datePicker ! bookingData.dateFrom.plusDays(1) datePicker ! bookingData.dateFrom.plusDays(1)
} answer { } onReply {
case Msg(cmd: Command, _) => case Msg(cmd: Command, _) =>
datePicker ! cmd datePicker ! cmd
stay() stay()
case Msg(date: ZonedDateTime, bookingData: BookingData) => case Msg(date: ZonedDateTime, bookingData: BookingData) =>
goto(requestDayTime) using bookingData.copy(dateTo = date) goto(requestAction) using bookingData.copy(dateTo = date)
}
private def requestDayTime: Step =
question { _ =>
bot.sendMessage(userId.source, lang.chooseTimeOfDay,
inlineKeyboard = createInlineKeyboard(lang.timeOfDay.map { case (id, label) => Button(label, id.toString) }.toSeq, columns = 1))
} answer {
case Msg(Command(_, msg, Some(timeIdStr)), bookingData: BookingData) =>
val timeId = timeIdStr.toInt
bot.sendEditMessage(userId.source, msg.messageId, lang.preferredTimeIs(timeId))
goto(requestAction) using bookingData.copy(timeOfDay = timeId)
} }
private def requestAction: Step = private def requestAction: Step =
question { bookingData => ask { bookingData =>
dataService.storeAppointment(userId.accountId, bookingData) dataService.storeAppointment(userId.accountId, bookingData)
bot.sendMessage(userId.source, bot.sendMessage(userId.source,
lang.bookingSummary(bookingData), lang.bookingSummary(bookingData),
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.findTerms, Tags.FindTerms), Button(lang.modifyDate, Tags.ModifyDate)))) inlineKeyboard = createInlineKeyboard(
} answer { Seq(Button(lang.findTerms, Tags.FindTerms), Button(lang.modifyDate, Tags.ModifyDate))
))
} onReply {
case Msg(CallbackCommand(Tags.FindTerms), _) => case Msg(CallbackCommand(Tags.FindTerms), _) =>
goto(requestTerm) goto(requestTerm)
case Msg(CallbackCommand(Tags.ModifyDate), _) => case Msg(CallbackCommand(Tags.ModifyDate), _) =>
@@ -136,14 +127,14 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
} }
private def requestTerm: Step = private def requestTerm: Step =
question { bookingData => ask { bookingData =>
val availableTerms = apiService.getAvailableTerms(userId.accountId, bookingData.cityId.id, val availableTerms = apiService.getAvailableTerms(userId.accountId, bookingData.cityId.id,
bookingData.clinicId.optionalId, bookingData.serviceId.id, bookingData.doctorId.optionalId, bookingData.clinicId.optionalId, bookingData.serviceId.id, bookingData.doctorId.optionalId,
bookingData.dateFrom, Some(bookingData.dateTo), timeOfDay = bookingData.timeOfDay) bookingData.dateFrom, Some(bookingData.dateTo), timeOfDay = bookingData.timeOfDay)
termsPager ! InitConversation termsPager ! InitConversation
termsPager ! StartConversation termsPager ! StartConversation
termsPager ! availableTerms termsPager ! availableTerms
} answer { } onReply {
case Msg(cmd: Command, _) => case Msg(cmd: Command, _) =>
termsPager ! cmd termsPager ! cmd
stay() stay()
@@ -163,10 +154,10 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
} }
private def askNoTermsAction: Step = private def askNoTermsAction: Step =
question { _ => ask { _ =>
bot.sendMessage(userId.source, lang.noTermsFound, inlineKeyboard = bot.sendMessage(userId.source, lang.noTermsFound, inlineKeyboard =
createInlineKeyboard(Seq(Button(lang.modifyDate, Tags.ModifyDate), Button(lang.createMonitoring, Tags.CreateMonitoring)))) createInlineKeyboard(Seq(Button(lang.modifyDate, Tags.ModifyDate), Button(lang.createMonitoring, Tags.CreateMonitoring))))
} answer { } onReply {
case Msg(CallbackCommand(Tags.ModifyDate), _) => case Msg(CallbackCommand(Tags.ModifyDate), _) =>
goto(requestDateFrom) goto(requestDateFrom)
case Msg(CallbackCommand(Tags.CreateMonitoring), _) => case Msg(CallbackCommand(Tags.CreateMonitoring), _) =>
@@ -203,17 +194,17 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
} }
private def askMonitoringOptions: Step = private def askMonitoringOptions: Step =
question { _ => ask { _ =>
bot.sendMessage(userId.source, lang.chooseTypeOfMonitoring, bot.sendMessage(userId.source, lang.chooseTypeOfMonitoring,
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.bookByApplication, Tags.BookByApplication), Button(lang.bookManually, Tags.BookManually)), columns = 1)) inlineKeyboard = createInlineKeyboard(Seq(Button(lang.bookByApplication, Tags.BookByApplication), Button(lang.bookManually, Tags.BookManually)), columns = 1))
} answer { } onReply {
case Msg(CallbackCommand(autobookStr), bookingData: BookingData) => case Msg(CallbackCommand(autobookStr), bookingData: BookingData) =>
val autobook = autobookStr.toBoolean val autobook = autobookStr.toBoolean
goto(createMonitoring) using bookingData.copy(autobook = autobook) goto(createMonitoring) using bookingData.copy(autobook = autobook)
} }
private def createMonitoring: Step = private def createMonitoring: Step =
internalConfig { bookingData => process { bookingData =>
debug(s"Creating monitoring for $bookingData") debug(s"Creating monitoring for $bookingData")
try { try {
monitoringService.createMonitoring((userId -> bookingData).mapTo[Monitoring]) monitoringService.createMonitoring((userId -> bookingData).mapTo[Monitoring])

View File

@@ -40,11 +40,13 @@ class Bug(val userId: UserId, bot: Bot, dataService: DataService, bugPagerActorF
private val bugPager = bugPagerActorFactory(userId, self) private val bugPager = bugPagerActorFactory(userId, self)
entryPoint(askAction)
def askAction: Step = def askAction: Step =
question { _ => ask { _ =>
bot.sendMessage(userId.source, lang.bugAction, inlineKeyboard = bot.sendMessage(userId.source, lang.bugAction, inlineKeyboard =
createInlineKeyboard(Seq(Button(lang.createNewBug, Tags.SubmitNew), Button(lang.showSubmittedBugs, Tags.ListSubmitted)))) createInlineKeyboard(Seq(Button(lang.createNewBug, Tags.SubmitNew), Button(lang.showSubmittedBugs, Tags.ListSubmitted))))
} answer { } onReply {
case Msg(Command(_, _, Some(Tags.SubmitNew)), _) => case Msg(Command(_, _, Some(Tags.SubmitNew)), _) =>
goto(askBugDescription) goto(askBugDescription)
case Msg(Command(_, _, Some(Tags.ListSubmitted)), _) => case Msg(Command(_, _, Some(Tags.ListSubmitted)), _) =>
@@ -52,7 +54,7 @@ class Bug(val userId: UserId, bot: Bot, dataService: DataService, bugPagerActorF
} }
def displaySubmittedBugs: Step = def displaySubmittedBugs: Step =
internalConfig { _ => process { _ =>
val bugs = dataService.getBugs(userId.userId) val bugs = dataService.getBugs(userId.userId)
bugPager ! InitConversation bugPager ! InitConversation
bugPager ! StartConversation bugPager ! StartConversation
@@ -71,16 +73,14 @@ class Bug(val userId: UserId, bot: Bot, dataService: DataService, bugPagerActorF
} }
def askBugDescription: Step = def askBugDescription: Step =
question { _ => ask { _ =>
bot.sendMessage(userId.source, lang.enterIssueDetails) bot.sendMessage(userId.source, lang.enterIssueDetails)
} answer { } onReply {
case Msg(MessageExtractors.TextCommand(details), _) => case Msg(MessageExtractors.TextCommand(details), _) =>
val bugId = dataService.submitBug(userId.userId, userId.source.sourceSystem.id, details) val bugId = dataService.submitBug(userId.userId, userId.source.sourceSystem.id, details)
bot.sendMessage(userId.source, lang.bugHasBeenCreated(bugId.getOrElse(-1L))) bot.sendMessage(userId.source, lang.bugHasBeenCreated(bugId.getOrElse(-1L)))
end() end()
} }
entryPoint(askAction)
} }
object Bug { object Bug {

View File

@@ -109,7 +109,7 @@ class Chat(val userId: UserId, dataService: DataService, monitoringService: Moni
stay() stay()
} }
private def actorDialogue(actor: ActorRef)(mainStateFunction: AnswerFn): Step = private def actorDialogue(actor: ActorRef)(mainStateFunction: MessageProcessorFn): Step =
monologue { monologue {
case event: Msg => case event: Msg =>
if (mainStateFunction.isDefinedAt(event)) mainStateFunction(event) if (mainStateFunction.isDefinedAt(event)) mainStateFunction(event)
@@ -119,7 +119,7 @@ class Chat(val userId: UserId, dataService: DataService, monitoringService: Moni
} }
} }
private def secondaryState(actor: ActorRef): AnswerFn = { private def secondaryState(actor: ActorRef): MessageProcessorFn = {
case Msg(cmd@TextCommand("/bug"), _) => case Msg(cmd@TextCommand("/bug"), _) =>
self ! cmd self ! cmd
goto(bugChat) goto(bugChat)

View File

@@ -49,7 +49,7 @@ class DatePicker(val userId: UserId, val bot: Bot, val localization: Localizatio
entryPoint(configure) entryPoint(configure)
def configure: Step = def configure: Step =
externalConfig { monologue {
case Msg(newMode: Mode, _) => case Msg(newMode: Mode, _) =>
mode = newMode mode = newMode
stay() stay()
@@ -58,13 +58,13 @@ class DatePicker(val userId: UserId, val bot: Bot, val localization: Localizatio
} }
def requestDate: Step = def requestDate: Step =
question { initialDate => ask { initialDate =>
val message = mode match { val message = mode match {
case DateFromMode => lang.chooseDateFrom case DateFromMode => lang.chooseDateFrom
case DateToMode => lang.chooseDateTo case DateToMode => lang.chooseDateTo
} }
bot.sendMessage(userId.source, message, inlineKeyboard = dateButtons(initialDate)) bot.sendMessage(userId.source, message, inlineKeyboard = dateButtons(initialDate))
} answer { } onReply {
case Msg(Command(_, msg, Some(Tags.Done)), finalDate) => case Msg(Command(_, msg, Some(Tags.Done)), finalDate) =>
val (message, updatedDate) = mode match { val (message, updatedDate) = mode match {
case DateFromMode => case DateFromMode =>

View File

@@ -40,7 +40,7 @@ class History(val userId: UserId, bot: Bot, apiService: ApiService, val localiza
entryPoint(prepareData) entryPoint(prepareData)
def prepareData: Step = def prepareData: Step =
internalConfig { _ => process { _ =>
val visits = apiService.visitsHistory(userId.accountId) val visits = apiService.visitsHistory(userId.accountId)
historyPager ! InitConversation historyPager ! InitConversation
historyPager ! StartConversation historyPager ! StartConversation

View File

@@ -49,23 +49,23 @@ class Login(source: MessageSource, bot: Bot, dataService: DataService, apiServic
} }
def requestUsername: Step = def requestUsername: Step =
question { _ => ask { _ =>
bot.sendMessage(source, lang.provideUsername) bot.sendMessage(source, lang.provideUsername)
} answer { } onReply {
case Msg(MessageExtractors.OptionalTextCommand(username), _) => case Msg(MessageExtractors.OptionalTextCommand(username), _) =>
goto(requestPassword) using LoginData(username = username) goto(requestPassword) using LoginData(username = username)
} }
def requestPassword: Step = def requestPassword: Step =
question { _ => ask { _ =>
bot.sendMessage(source, lang.providePassword) bot.sendMessage(source, lang.providePassword)
} answer { } onReply {
case Msg(MessageExtractors.OptionalTextCommand(password), loginData: LoginData) => case Msg(MessageExtractors.OptionalTextCommand(password), loginData: LoginData) =>
goto(processLoginInformation) using loginData.copy(password = password.map(textEncryptor.encrypt)) goto(processLoginInformation) using loginData.copy(password = password.map(textEncryptor.encrypt))
} }
def processLoginInformation: Step = { def processLoginInformation: Step = {
internalConfig { case LoginData(Some(username), Some(password)) => process { case LoginData(Some(username), Some(password)) =>
val loginResult = apiService.login(username, password) val loginResult = apiService.login(username, password)
loginResult match { loginResult match {
case Left(error) => case Left(error) =>

View File

@@ -41,7 +41,7 @@ class Monitorings(val userId: UserId, bot: Bot, monitoringService: MonitoringSer
entryPoint(prepareData) entryPoint(prepareData)
def prepareData: Step = def prepareData: Step =
internalConfig { _ => process { _ =>
val monitorings = monitoringService.getActiveMonitorings(userId.accountId) val monitorings = monitoringService.getActiveMonitorings(userId.accountId)
monitoringsPager ! InitConversation monitoringsPager ! InitConversation
monitoringsPager ! StartConversation monitoringsPager ! StartConversation
@@ -62,10 +62,10 @@ class Monitorings(val userId: UserId, bot: Bot, monitoringService: MonitoringSer
} }
def askToDeactivateMonitoring: Step = def askToDeactivateMonitoring: Step =
question { monitoring => ask { monitoring =>
bot.sendMessage(userId.source, lang.deactivateMonitoring(monitoring), inlineKeyboard = bot.sendMessage(userId.source, lang.deactivateMonitoring(monitoring), inlineKeyboard =
createInlineKeyboard(Seq(Button(lang.no, Tags.No), Button(lang.yes, Tags.Yes)))) createInlineKeyboard(Seq(Button(lang.no, Tags.No), Button(lang.yes, Tags.Yes))))
} answer { } onReply {
case Msg(Command(_, _, Some(Tags.No)), _) => case Msg(Command(_, _, Some(Tags.No)), _) =>
bot.sendMessage(userId.source, lang.monitoringWasNotDeactivated) bot.sendMessage(userId.source, lang.monitoringWasNotDeactivated)
end() end()

View File

@@ -43,7 +43,7 @@ class Pager[Data](val userId: UserId, bot: Bot, makeMessage: (Data, Int, Int) =>
entryPoint(awaitForData) entryPoint(awaitForData)
private def awaitForData: Step = private def awaitForData: Step =
externalConfig { monologue {
case Msg(Left(error: Throwable), _) => case Msg(Left(error: Throwable), _) =>
bot.sendMessage(userId.source, error.getMessage) bot.sendMessage(userId.source, error.getMessage)
end() end()
@@ -55,9 +55,9 @@ class Pager[Data](val userId: UserId, bot: Bot, makeMessage: (Data, Int, Int) =>
} }
private def displayPage: Step = private def displayPage: Step =
question { case (registry, massageIdMaybe) => ask { case (registry, massageIdMaybe) =>
sendPage(registry.page, registry.pages, massageIdMaybe) sendPage(registry.page, registry.pages, massageIdMaybe)
} answer { } onReply {
case Msg(Command(_, msg, Some(Tags.Next)), (registry, _)) => case Msg(Command(_, msg, Some(Tags.Next)), (registry, _)) =>
val page = registry.page + 1 val page = registry.page + 1
goto(displayPage) using registry.copy(page = page) -> Some(msg.messageId) goto(displayPage) using registry.copy(page = page) -> Some(msg.messageId)

View File

@@ -37,19 +37,19 @@ class Settings(val userId: UserId, bot: Bot, dataService: DataService, val local
entryPoint(askForAction) entryPoint(askForAction)
def askForAction: Step = def askForAction: Step =
question { _ => ask { _ =>
bot.sendMessage(userId.source, lang.settingsHeader, inlineKeyboard = bot.sendMessage(userId.source, lang.settingsHeader, inlineKeyboard =
createInlineKeyboard(Seq(Button(lang.language, Tags.Language)))) createInlineKeyboard(Seq(Button(lang.language, Tags.Language))))
} answer { } onReply {
case Msg(Command(_, _, Some(Tags.Language)), _) => case Msg(Command(_, _, Some(Tags.Language)), _) =>
goto(askLanguage) goto(askLanguage)
} }
def askLanguage: Step = def askLanguage: Step =
question { _ => ask { _ =>
bot.sendMessage(userId.source, lang.chooseLanguage, bot.sendMessage(userId.source, lang.chooseLanguage,
inlineKeyboard = createInlineKeyboard(Lang.Langs.map(l => Button(l.label, l.id)), columns = 1)) inlineKeyboard = createInlineKeyboard(Lang.Langs.map(l => Button(l.label, l.id)), columns = 1))
} answer { } onReply {
case Msg(Command(_, _, Some(langIdStr)), _) => case Msg(Command(_, _, Some(langIdStr)), _) =>
val langId = langIdStr.toInt val langId = langIdStr.toInt
localization.updateLanguage(userId.userId, Lang(langId)) localization.updateLanguage(userId.userId, Lang(langId))

View File

@@ -41,16 +41,16 @@ class StaticData(val userId: UserId, bot: Bot, val localization: Localization, o
entryPoint(AwaitConfig) entryPoint(AwaitConfig)
def AwaitConfig: Step = def AwaitConfig: Step =
externalConfig { monologue {
case Msg(newConfig: StaticDataConfig, _) => case Msg(newConfig: StaticDataConfig, _) =>
config = newConfig config = newConfig
goto(askForLatestOption) goto(askForLatestOption)
} }
def askForLatestOption: Step = def askForLatestOption: Step =
question { _ => ask { _ =>
originator ! LatestOptions originator ! LatestOptions
} answer { } onReply {
case Msg(LatestOptions(options), _) if options.isEmpty => case Msg(LatestOptions(options), _) if options.isEmpty =>
val callbackTags = anySelectOption val callbackTags = anySelectOption
goto(askForUserInput) using callbackTags goto(askForUserInput) using callbackTags
@@ -60,10 +60,10 @@ class StaticData(val userId: UserId, bot: Bot, val localization: Localization, o
} }
def askForUserInput: Step = def askForUserInput: Step =
question { callbackTags => ask { callbackTags =>
bot.sendMessage(userId.source, lang.pleaseEnterStaticDataNameOrPrevious(config), bot.sendMessage(userId.source, lang.pleaseEnterStaticDataNameOrPrevious(config),
inlineKeyboard = createInlineKeyboard(callbackTags, columns = 1)) inlineKeyboard = createInlineKeyboard(callbackTags, columns = 1))
} answer { } onReply {
case Msg(Command(_, msg, Some(tag)), callbackTags) => case Msg(Command(_, msg, Some(tag)), callbackTags) =>
val id = tag.toLong val id = tag.toLong
val label = callbackTags.find(_.tag == tag).map(_.label).getOrElse(sys.error("Unable to get callback tag label")) val label = callbackTags.find(_.tag == tag).map(_.label).getOrElse(sys.error("Unable to get callback tag label"))

View File

@@ -35,7 +35,7 @@ trait StaticDataForBooking extends Conversation[BookingData] {
private[actor] def staticData: ActorRef private[actor] def staticData: ActorRef
protected def withFunctions(latestOptions: => Seq[IdName], staticOptions: => Either[Throwable, List[IdName]], applyId: IdName => BookingData): Step => AnswerFn = { protected def withFunctions(latestOptions: => Seq[IdName], staticOptions: => Either[Throwable, List[IdName]], applyId: IdName => BookingData): Step => MessageProcessorFn = {
nextStep: Step => { nextStep: Step => {
case Msg(cmd: Command, _) => case Msg(cmd: Command, _) =>
staticData ! cmd staticData ! cmd
@@ -51,12 +51,12 @@ trait StaticDataForBooking extends Conversation[BookingData] {
} }
} }
protected def staticData(staticDataConfig: => StaticDataConfig)(functions: BookingData => Step => AnswerFn)(requestNext: Step): Step = { protected def staticData(staticDataConfig: => StaticDataConfig)(functions: BookingData => Step => MessageProcessorFn)(requestNext: Step): Step = {
question { _ => ask { _ =>
staticData ! InitConversation staticData ! InitConversation
staticData ! StartConversation staticData ! StartConversation
staticData ! staticDataConfig staticData ! staticDataConfig
} answer { } onReply {
case msg@Msg(_, bookingData: BookingData) => case msg@Msg(_, bookingData: BookingData) =>
val fn = functions(bookingData)(requestNext) val fn = functions(bookingData)(requestNext)
fn(msg) fn(msg)

View File

@@ -42,7 +42,7 @@ class Visits(val userId: UserId, bot: Bot, apiService: ApiService, val localizat
entryPoint(prepareData) entryPoint(prepareData)
def prepareData: Step = def prepareData: Step =
internalConfig { _ => process { _ =>
val visits = apiService.reservedVisits(userId.accountId) val visits = apiService.reservedVisits(userId.accountId)
reservedVisitsPager ! InitConversation reservedVisitsPager ! InitConversation
reservedVisitsPager ! StartConversation reservedVisitsPager ! StartConversation
@@ -63,10 +63,10 @@ class Visits(val userId: UserId, bot: Bot, apiService: ApiService, val localizat
} }
def askToCancelVisit: Step = def askToCancelVisit: Step =
question { visit => ask { visit =>
bot.sendMessage(userId.source, lang.areYouSureToCancelAppointment(visit), bot.sendMessage(userId.source, lang.areYouSureToCancelAppointment(visit),
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.no, Tags.No), Button(lang.yes, Tags.Yes)))) inlineKeyboard = createInlineKeyboard(Seq(Button(lang.no, Tags.No), Button(lang.yes, Tags.Yes))))
} answer { } onReply {
case Msg(Command(_, _, Some(Tags.No)), _) => case Msg(Command(_, _, Some(Tags.No)), _) =>
bot.sendMessage(userId.source, lang.appointmentWasNotCancelled) bot.sendMessage(userId.source, lang.appointmentWasNotCancelled)
end() end()

View File

@@ -15,13 +15,13 @@ trait Conversation[D] extends Actor with Domain[D] with Logger {
private var startWithStep: Step = _ private var startWithStep: Step = _
private val defaultMsgHandler: AnswerFn = { private val defaultMsgHandler: MessageProcessorFn = {
case Msg(any, data) => case Msg(any, data) =>
debug(s"Unhandled message received. [$any, $data]") debug(s"Unhandled message received. [$any, $data]")
NextStep(currentStep, Some(data)) NextStep(currentStep, Some(data))
} }
private var msgHandler: AnswerFn = defaultMsgHandler private var msgHandler: MessageProcessorFn = defaultMsgHandler
private var runAfterInit: () => Unit = () => {} private var runAfterInit: () => Unit = () => {}
@@ -34,8 +34,8 @@ trait Conversation[D] extends Actor with Domain[D] with Logger {
def execute(): Unit = { def execute(): Unit = {
try { try {
currentStep match { currentStep match {
case qa: QuestionAnswer => qa.question.questionFn(currentData) case qa: Dialogue => qa.askFn(currentData)
case InternalConfiguration(fn) => case Process(fn) =>
val nextStep = fn(currentData) val nextStep = fn(currentData)
moveToNextStep(nextStep) moveToNextStep(nextStep)
case _ => //do nothing case _ => //do nothing
@@ -56,10 +56,7 @@ trait Conversation[D] extends Actor with Domain[D] with Logger {
} }
currentStep match { currentStep match {
case ExternalConfiguration(fn) => case Dialogue(_, fn) =>
val conf = Msg(any, currentData)
handle(conf, fn, msgHandler)
case QuestionAnswer(_, Answer(fn)) =>
val fact = Msg(any, currentData) val fact = Msg(any, currentData)
handle(fact, fn, msgHandler) handle(fact, fn, msgHandler)
case Monologue(fn) => case Monologue(fn) =>
@@ -87,13 +84,11 @@ trait Conversation[D] extends Actor with Domain[D] with Logger {
init() init()
} }
protected def monologue(answerFn: AnswerFn): Monologue = Monologue(answerFn) protected def monologue(answerFn: MessageProcessorFn): Monologue = Monologue(answerFn)
protected def question(questionFn: D => Unit): Question = Question(questionFn) protected def ask(askFn: D => Unit): Ask = Ask(askFn)
protected def externalConfig(receiveConfFunction: ExternalConfigFn): ExternalConfiguration = ExternalConfiguration(receiveConfFunction) protected def process(processFn: ProcessFn): Process = Process(processFn)
protected def internalConfig(receiveConfFunction: InternalConfigFn): InternalConfiguration = InternalConfiguration(receiveConfFunction)
protected def end(): NextStep = NextStep(End) protected def end(): NextStep = NextStep(End)
@@ -104,7 +99,7 @@ trait Conversation[D] extends Actor with Domain[D] with Logger {
protected def stay(): NextStep = NextStep(currentStep) protected def stay(): NextStep = NextStep(currentStep)
protected def whenUnhandledMsg(receiveMsgFn: AnswerFn): Unit = { protected def whenUnhandledMsg(receiveMsgFn: MessageProcessorFn): Unit = {
msgHandler = receiveMsgFn orElse defaultMsgHandler msgHandler = receiveMsgFn orElse defaultMsgHandler
} }

View File

@@ -1,13 +1,11 @@
package com.lbs.server.actor.conversation package com.lbs.server.actor.conversation
trait Domain[D] { trait Domain[D] {
protected type QuestionFn = D => Unit protected type AskFn = D => Unit
protected type AnswerFn = PartialFunction[Msg, NextStep] protected type MessageProcessorFn = PartialFunction[Msg, NextStep]
protected type ExternalConfigFn = AnswerFn protected type ProcessFn = D => NextStep
protected type InternalConfigFn = D => NextStep
protected case class Msg(message: Any, data: D) protected case class Msg(message: Any, data: D)
@@ -15,22 +13,18 @@ trait Domain[D] {
private[conversation] object End extends Step private[conversation] object End extends Step
protected case class ExternalConfiguration(configFn: ExternalConfigFn) extends Step protected case class Process(processFn: ProcessFn) extends Step
protected case class InternalConfiguration(configFn: InternalConfigFn) extends Step protected case class Dialogue(askFn: AskFn, replyProcessorFn: MessageProcessorFn) extends Step
protected case class QuestionAnswer(question: Question, answer: Answer) extends Step protected case class Monologue(replyProcessorFn: MessageProcessorFn) extends Step
protected case class Monologue(answerFn: AnswerFn) extends Step
private[conversation] case class NextStep(step: Step, data: Option[D] = None) private[conversation] case class NextStep(step: Step, data: Option[D] = None)
private[conversation] case class Question(questionFn: QuestionFn) private[conversation] case class Ask(askFn: AskFn)
private[conversation] case class Answer(answerFn: AnswerFn) protected implicit class RichQuestion(ask: Ask) {
def onReply(replyProcessorFn: MessageProcessorFn): Dialogue = Dialogue(ask.askFn, replyProcessorFn)
protected implicit class RichQuestion(question: Question) {
def answer(answerFn: AnswerFn): QuestionAnswer = QuestionAnswer(question, Answer(answerFn))
} }
protected implicit class NextStepOps(nextStep: NextStep) { protected implicit class NextStepOps(nextStep: NextStep) {

View File

@@ -23,32 +23,32 @@ class ConversationSpec extends AkkaTestKit {
private var conf: String = _ private var conf: String = _
def configure: Step = def configure: Step =
externalConfig { monologue {
case Msg(confStr: String, data) => case Msg(confStr: String, data) =>
conf = confStr conf = confStr
goto(askHello) using data.copy(configured = true) goto(askHello) using data.copy(configured = true)
} }
def askHello: Step = def askHello: Step =
question { data => ask { data =>
self ! Hello self ! Hello
} answer { } onReply {
case Msg(Hello, data) => case Msg(Hello, data) =>
goto(askWorld) using data.copy(hello = "hello") goto(askWorld) using data.copy(hello = "hello")
} }
def askWorld: Step = def askWorld: Step =
question { data => ask { data =>
self ! World self ! World
} answer { } onReply {
case Msg(World, data) => case Msg(World, data) =>
goto(askDialogue) using data.copy(world = "world") goto(askDialogue) using data.copy(world = "world")
} }
def askDialogue: Step = def askDialogue: Step =
question { data => ask { data =>
self ! Dialogue self ! Dialogue
} answer { } onReply {
case Msg(Dialogue, data) => case Msg(Dialogue, data) =>
originator ! data.copy(people = "dialogue") -> conf originator ! data.copy(people = "dialogue") -> conf
end() end()
@@ -81,19 +81,19 @@ class ConversationSpec extends AkkaTestKit {
class TestActor(originator: ActorRef) extends Conversation[Data] { class TestActor(originator: ActorRef) extends Conversation[Data] {
def configure1: Step = def configure1: Step =
internalConfig { _ => process { _ =>
goto(configure2) using Data(configured = true) goto(configure2) using Data(configured = true)
} }
def configure2: Step = def configure2: Step =
internalConfig { data => process { data =>
goto(askMessage2) using data.copy(message1 = "hello") goto(askMessage2) using data.copy(message1 = "hello")
} }
def askMessage2: Step = def askMessage2: Step =
question { _ => ask { _ =>
self ! InvokeEnrichMessage self ! InvokeEnrichMessage
} answer { } onReply {
case Msg(InvokeEnrichMessage, data) => case Msg(InvokeEnrichMessage, data) =>
originator ! data.copy(message2 = "world") originator ! data.copy(message2 = "world")
end() end()