/* * Copyright (C) 2004-2006 Jive Software. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.xmpp.workgroup.chatbot; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.jivesoftware.openfire.fastpath.dataforms.FormElement; import org.jivesoftware.openfire.fastpath.dataforms.FormManager; import org.jivesoftware.openfire.fastpath.dataforms.WorkgroupForm; import org.jivesoftware.openfire.fastpath.history.ChatTranscriptManager; import org.jivesoftware.openfire.fastpath.settings.chat.ChatSettings; import org.jivesoftware.openfire.fastpath.settings.chat.ChatSettingsManager; import org.jivesoftware.openfire.fastpath.settings.chat.KeyEnum; import org.jivesoftware.util.NotFoundException; import org.jivesoftware.xmpp.workgroup.UnauthorizedException; import org.jivesoftware.xmpp.workgroup.UserCommunicationMethod; import org.jivesoftware.xmpp.workgroup.Workgroup; import org.jivesoftware.xmpp.workgroup.interceptor.ChatbotInterceptorManager; import org.jivesoftware.xmpp.workgroup.interceptor.InterceptorManager; import org.jivesoftware.xmpp.workgroup.interceptor.PacketRejectedException; import org.jivesoftware.xmpp.workgroup.interceptor.QueueInterceptorManager; import org.jivesoftware.xmpp.workgroup.request.Request; import org.jivesoftware.xmpp.workgroup.request.UserRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmpp.packet.JID; import org.xmpp.packet.Message; /** * A Chatbot holds a sequence of steps where each step represents an interaction with a * user. The user may navigate sequentially between the defined steps in the Chatbot.<p> * * ChatBots are stateless so the information of the chat for each user is kept in a * {@link ChatbotSession}. Each {@link org.jivesoftware.xmpp.workgroup.Workgroup} will have * a different Chatbot.<p> * * Currently the bot steps are hardcoded but a future version may let them be dynamic. The * current step is kept in the ChatbotSession as an integer number. When filling out a data * form a substep is used to register the current question that the user is completing. * These are the current supported steps: * <ol> * <li><b>step = -1</b> - The chat session was just created but no message was sent.</li> * <li><b>step = 0</b> - The welcome message was sent to the user. Since the user will receive the * join question after the welcome message then this position is changed immediatelly.</li> * <li><b>step = 1</b> - The join question was sent to the user and an answer is expected.</li> * <li><b>step = 2</b> - The user decided to join the workgroup so the dataform is presented to * the user. Each field in the dataform will generate a new message for the user. After the * question was answered then the next message is sent. The same step value will be used for all * the dataform fields. For each question a substep is used to represent the current question that * the user is completing. The user may go back or repeat the question by sending commands.</li> * <li><b>step = 3</b> - The user completed the form and joined a queue. At this point only * commands are accepted from the user.</li> * <li><b>step = 4</b> - The user left the queue since a room invitation was sent to him to start * a chat session with an agent. At this point the user may ask to receive the invitation * again.</li> * <li><b>step = 5</b> - The user accepted the invitation and is now having a chat with an agent. * At this point only commands are accepted from the user.</li> * <li><b>step = 6</b> - The user has finished the chat with the agent and is asked if he wants to * receive the chat transcript by email.</li> * <li><b>step = 7</b> - The user wants to get the transcript by email but hasn't specified an * email address so ask him to enter an email address and then send the chat transcript by * email.</li> * </ol> * * The Chatbot allows the user to execute commands. Commands may help the user go back to * previous questions so he can change an answer or repeat the last question in case the user * closed his window accidentally. * These are the supported commands: * <ol> * <li><b>back</b> - User is requesting to go back one step in the dialog and repeat the * previous message</li> * <li><b>repeat</b> - User is requesting to repeat the last message</li> * <li><b>help</b> - User is requesting the list of available commands</li> * <li><b>bye</b> - User is closing the chat session no matter the if he already joined a * waiting queue or not</li> * <li><b>position</b> - User wants to know his position in the waiting queue</li> * </ol> * * <b>Future ideas</b> * <ul> * <li>Configure the chat bot with JIDs of agents or admin that are allowed to:<ol> * <li>obtain workgroup and queue statistics</li> * <li>obtain current workgroup configureation</li> * <li>change the chatbot configuration</li> * <li>change the workgroup configuration, etc. etc. etc.</li></ol></li> * <li>After the user ends the chat session with the agent offer the user to fill out * a QoS survey.</li> * </ul> * * @author Gaston Dombiak */ public class Chatbot implements UserCommunicationMethod { private static final Logger Log = LoggerFactory.getLogger(Chatbot.class); /** * Holds the workgroup where the chatbot is working. This is a one-to-one relation so this * chatbot is the only chatbot that will be answering Messages sent to the workgroup. */ private Workgroup workgroup; /** * The chat settings stores all the messages that the chatbot will use. The messages can * be modified from the Admin Console. */ private ChatSettings settings; /** * Holds the chat sessions of all the users that are trying to join this workgroup. */ private Map<String, ChatbotSession> sessions = new ConcurrentHashMap<String, ChatbotSession>(); // TODO use resource bundles for these joining answers /** * Text that assumes that a user sent a positive answer. */ private String yes = "yes"; /** * Text that assumes that a user sent a negative answer. */ private String no = "no"; /** * Creates a new chatbot responsible for answering Messages sent to the specified workgroup. * * @param workgroup Workgroup where the new chatbot will be working. */ public Chatbot(Workgroup workgroup) { this.workgroup = workgroup; this.settings = ChatSettingsManager.getInstance().getChatSettings(workgroup); } /** * Returns the session of the specified user in a given workgroup. If no session exists then * a new one will be created for the user if requested. If the session of the user remains * inactive for some time then it may be discarded. * * @param user the user to return his session in a given workgroup. * @param create true if a new session should be created if it doesn't already exist. * @return the session of the specified user in a given workgroup. */ public ChatbotSession getSession(JID user, boolean create) { String fullJID = user.toString(); ChatbotSession session = sessions.get(fullJID); if (session == null && create) { synchronized (fullJID.intern()) { session = sessions.get(fullJID); if (session == null) { session = new ChatbotSession(user, this); sessions.put(fullJID, session); } } } return session; } /** * Process a message sent by the owner of the specified session. * * @param session the session whose owner (i.e. user) sent the message. * @param message message sent by the owner of the session to the workgroup. */ public void onMessage(ChatbotSession session, Message message) { InterceptorManager interceptorManager = ChatbotInterceptorManager.getInstance(); try { interceptorManager.invokeInterceptors(workgroup.getJID().toBareJID(), message, true, false); // Update the Message thread that the user is using in the session session.setMessageThread(message.getThread()); // Check if the workgroup is opened synchronized(session) { if (workgroup.getStatus() != Workgroup.Status.OPEN) { // Send message saying that the workgroup is closed/not available sendReply(message, getWorkgroupClosedMessage()); } else if (handleCommand(message, session)) { // The sent message executed a command so do nothing } else if (session.getCurrentStep() < 0) { // Send the welcome message sendWelcomeMessage(message); // Send the join question sendJoinQuestion(message, session); } else if (session.getCurrentStep() == 1) { // User is answering join question if (yes.equalsIgnoreCase(message.getBody().trim())) { // User accepted to join the workgroup so send the first question of // the form userAcceptedJoining(message, session); } else if (no.equalsIgnoreCase(message.getBody().trim())) { // User rejected to join the workgroup so send a goodbye message and close // the chat session closeSession(message); } else { // The user sent an unknown answer so repeat the join question sendJoinQuestion(message, session); } } else if (session.getCurrentStep() == 2) { // User is filling out the form if (userAnsweredField(message, session)) { // User answered correctly the question so send the next question or if the // form has been filled out then join the queue if (session.getCurrentSubstep() < getForm().getFormElements().size()-1) { sendNextQuestion(message, session); } else { userJoinQueue(message, session); } } else { // The user sent an unknown answer so repeat the last question repeatQuestion(message, session); } } else if (session.getCurrentStep() == 4) { // User is answering if he wants to get another room invitation if (yes.equalsIgnoreCase(message.getBody().trim())) { // User accepted to receive another room invitation so send another // room invitation sendRoomInvitation(message, session); } else if (no.equalsIgnoreCase(message.getBody().trim())) { // User declined to receive another room invitation so do nothing } else { // The user sent an unknown answer so repeat the invitation question sendInvitationQuestion(message.getFrom(), session); } } else if (session.getCurrentStep() == 6) { // User is answering email question if (yes.equalsIgnoreCase(message.getBody().trim())) { // User accepted to receive the transcript by email List<String> emailValue = session.getAttributes().get("email"); if (emailValue == null || emailValue.isEmpty()) { // The user wants to get the transcript by email but he hasn't provided // an email yet so ask for one sendGetEmailQuestion(message, session); } else { // Send the transcript by email sendTranscriptByMail(emailValue.get(0), message, session); // Send a goodbye message and close the chat session closeSession(message); } } else if (no.equalsIgnoreCase(message.getBody().trim())) { // User rejected to receive the transcript by email so send a goodbye // message and close the chat session closeSession(message); } else { // The user sent an unknown answer so repeat the email question sendEmailQuestion(message.getFrom(), session); } } else if (session.getCurrentStep() == 7) { // Send the transcript by email sendTranscriptByMail(message.getBody().trim(), message, session); // Send a goodbye message and close the chat session closeSession(message); } else { // User is waiting in a queue and the sent message contains an unkown content // so send message saying that the command was not acceptable (i.e. unknown command) sendReply(message, getNotAcceptableMessage()); } } interceptorManager.invokeInterceptors(workgroup.getJID().toBareJID(), message, true, true); } catch (PacketRejectedException e) { workgroup.rejectPacket(message, e); } } private void userJoinQueue(Message message, ChatbotSession session) { InterceptorManager interceptorManager = QueueInterceptorManager.getInstance(); try { interceptorManager.invokeInterceptors(workgroup.getJID().toBareJID(), message, true, false); if (getRoutingMessage() != null && getRoutingMessage().length() > 0) { sendReply(message, getRoutingMessage()); } // Set that we are currently joining a waiting queue session.setCurrentStep(3); // Received a Join Queue request from a visitor, create a new request. UserRequest request = new UserRequest(session, workgroup); // Let the workgroup process the new request if (!workgroup.queueRequest(request)) { // It was not possible to add the request to a queue so send message saying that // the workgroup is not accepting new join requests sendReply(message, getCannotJoinMessage()); // Send the goodbye message and close the session closeSession(message); } else { session.setRequest(request); } interceptorManager.invokeInterceptors(workgroup.getJID().toBareJID(), message, true, true); } catch (PacketRejectedException e) { workgroup.rejectPacket(message, e); } } private void userDepartQueue(Message message) { // Remove the user from the queue if he was waiting in the queue try { Request request = UserRequest.getRequest(workgroup, message.getFrom()); InterceptorManager interceptorManager = QueueInterceptorManager.getInstance(); try { interceptorManager.invokeInterceptors(workgroup.getJID().toBareJID(), message, true, false); request.cancel(Request.CancelType.DEPART); // Remove the session (the goodbye message is sent when leaving the queue) removeSession(message.getFrom()); interceptorManager.invokeInterceptors(workgroup.getJID().toBareJID(), message, true, true); } catch (PacketRejectedException e) { workgroup.rejectPacket(message, e); } } catch (NotFoundException e) { // Send the goodbye message and close the session closeSession(message); } } private void sendWelcomeMessage(Message message) { String welcomeMessage = getWelcomeMessage(); welcomeMessage = welcomeMessage.replace("${workgroup}", workgroup.getJID().toString()); sendReply(message, welcomeMessage); } private void sendJoinQuestion(Message message, ChatbotSession session) { sendReply(message, getJoinQuestion()); // Set that we are currently waiting for a response to the join question session.setCurrentStep(1); } private void sendPreviousQuestion(Message message, ChatbotSession session) { if (session.getCurrentSubstep() == 0) { sendJoinQuestion(message, session); } else { // Send the next question to the user sendQuestion(message, session, session.getCurrentSubstep()-1); } } private void sendNextQuestion(Message message, ChatbotSession session) { // Send the next question to the user sendQuestion(message, session, session.getCurrentSubstep()+1); } private void sendEmailQuestion(JID receiver, ChatbotSession session) { // Ask the user if he wants to receive the chat transcript by email session.setCurrentStep(6); sendMessage(receiver, session.getMessageThread(), getSendEmailQuestion()); } private void sendRoomInvitation(Message message, ChatbotSession session) { UserRequest request = session.getRequest(); // Send again the room invitation to the user workgroup.sendUserInvitiation(request, request.getInvitedRoomID()); // Send a confirmation to the user notifying that the invitation was sent sendMessage(message.getFrom(), session.getMessageThread(), getInvitationResentMessage()); } public void sendInvitationQuestion(JID receiver, ChatbotSession session) { sendMessage(receiver, session.getMessageThread(), getSendInvitationQuestion()); } private void sendGetEmailQuestion(Message message, ChatbotSession session) { // Ask the user for his email address session.setCurrentStep(7); sendMessage(message.getFrom(), session.getMessageThread(), getGetEmailQuestion()); } private void sendTranscriptByMail(String email, Message message, ChatbotSession session) { // Send the transcript by email ChatTranscriptManager.sendTranscriptByMail(session.getRequest().getSessionID(), email); // Confirm the user that the email was sent to his email address sendEmailSentConfirmation(email, message); } private void sendEmailSentConfirmation(String email, Message message) { String emailSentMessage = getEmailSentMessage(); emailSentMessage = emailSentMessage.replace("${email}", email); sendReply(message, emailSentMessage); } private void sendHelpMessage(Message message) { sendReply(message, getHelpHelpMessage()); sendReply(message, getBackHelpMessage()); sendReply(message, getRepeatHelpMessage()); sendReply(message, getByeHelpMessage()); sendReply(message, getPositionHelpMessage()); } /** * Repeat the last question to the user. This may happen either if because the user requested * it or because the user sent an invalid answer. * * @param message the sent message by the user. * @param session the session that keeps the state of the user chat. */ private void repeatQuestion(Message message, ChatbotSession session) { // Resend the last question to the user sendQuestion(message, session, session.getCurrentSubstep()); } private void userAcceptedJoining(Message message, ChatbotSession session) { if (getForm() != null && !getForm().getFormElements().isEmpty()) { if (getFilloutFormMessage() != null && getFilloutFormMessage().length() > 0) { // Send the message informing that a form must be filled out sendReply(message, getFilloutFormMessage()); } session.setCurrentStep(2); // Send the first question to the user sendQuestion(message, session, 0); } else { // Since there is no form to fill out just join the queue userJoinQueue(message, session); } } private void closeSession(Message message) { sendReply(message, getByeMessage()); // Remove the session of this user removeSession(message.getFrom()); } private void removeSession(JID user) { // Remove the session of this user sessions.remove(user.toString()); } private void sendQuestion(Message message, ChatbotSession session, int position) { FormElement field = getForm().getFormElementAt(position); if (field == null) { return; } if (field.getAnswerType() == WorkgroupForm.FormEnum.hidden) { // Auto accept hidden fields Message fakeMessage = message.createCopy(); StringBuilder builder = new StringBuilder(); for (Iterator<String> it=field.getAnswers().iterator(); it.hasNext();) { builder.append(it.next()); if (it.hasNext()) { builder.append("/"); } } fakeMessage.setBody(builder.toString()); // Set that we are currently waiting for a response to the next question session.setCurrentSubstep(position); // Simulate that the user sent this message (with the hidden field) onMessage(session, fakeMessage); } String text = field.getLabel(); if (field.getAnswerType() == WorkgroupForm.FormEnum.radio_button || field.getAnswerType() == WorkgroupForm.FormEnum.dropdown_box || field.getAnswerType() == WorkgroupForm.FormEnum.checkbox) { // Append the options to the message body if (!field.getAnswers().isEmpty()) { StringBuilder builder = new StringBuilder(text); builder.append(" ["); builder.append(Request.encodeMetadataValue(field.getAnswers())); builder.append("]"); text = builder.toString(); } } sendReply(message, text); // Set that we are currently waiting for a response to the next question session.setCurrentSubstep(position); } /** * Returns true if the received message contains a valid answer for the current question. * * @param message the sent message by the user. * @param session the session that keeps the state of the user chat. * @return true if the received message contains a valid answer for the current question. */ private boolean userAnsweredField(Message message, ChatbotSession session) { boolean validAnswer = false; List<String> answers = Request.decodeMetadataValue(message.getBody().trim()); FormElement field = getForm().getFormElementAt(session.getCurrentSubstep()); List<String> options = field.getAnswers(); if (!options.isEmpty()) { for (String answer : answers) { // Check that the each answer is an allowed option validAnswer = false; for (String option : options) { // Skip any CR character present in the option option = option.replace("\r", ""); if (option.equalsIgnoreCase(answer)) { validAnswer = true; break; } } if (!validAnswer) { return false; } } } else { // The question allows any value so we accept this answer validAnswer = true; } if (validAnswer) { // Store the answer since it's a valid answer session.putAttribute(field.getVariable(), answers); } return validAnswer; } /** * Returns true if the message sent by the user requested to run a command. Depending on the * stage of the conversation different commands may be executed. * * @param message the message. * @param session the session. * @return true if the message sent by the user requested to run a command. */ private boolean handleCommand(Message message, ChatbotSession session) { String command = message.getBody().trim(); if (getHelpCommand().equalsIgnoreCase(command)) { sendHelpMessage(message); return true; } else if (getByeCommand().equalsIgnoreCase(command)) { userDepartQueue(message); return true; } if (session.getCurrentStep() == 1) { if (getRepeatCommand().equalsIgnoreCase(command)) { // Send the join question sendJoinQuestion(message, session); return true; } else if (getPositionCommand().equalsIgnoreCase(command)) { // Tell the user that he is not waiting in the queue sendReply(message, getNotInQueueMessage()); return true; } } else if (session.getCurrentStep() == 2) { if (getBackCommand().equalsIgnoreCase(command)) { sendPreviousQuestion(message, session); return true; } else if (getRepeatCommand().equalsIgnoreCase(command)) { // Resend the last question repeatQuestion(message, session); return true; } else if (getPositionCommand().equalsIgnoreCase(command)) { // Tell the user that he is not waiting in the queue sendReply(message, getNotInQueueMessage()); return true; } } else if (session.getCurrentStep() == 3) { if (getPositionCommand().equalsIgnoreCase(command)) { try { UserRequest request = UserRequest.getRequest(workgroup, message.getFrom()); request.updateQueueStatus(true); } catch (NotFoundException e) { // Tell the user that he is not waiting in the queue sendReply(message, getNotInQueueMessage()); } return true; } } else if (session.getCurrentStep() == 6) { if (getRepeatCommand().equalsIgnoreCase(command)) { // Resend the last question sendEmailQuestion(message.getFrom(), session); return true; } } else if (session.getCurrentStep() == 7) { if (getRepeatCommand().equalsIgnoreCase(command)) { // Resend the last question sendGetEmailQuestion(message, session); return true; } } return false; } private void sendReply(Message message, String reply) { Message packet = new Message(); packet.setTo(message.getFrom()); packet.setFrom(message.getTo()); packet.setThread(message.getThread()); packet.setType(message.getType()); packet.setBody(reply); send(packet); } private void sendMessage(JID receiver, String thread, String body) { Message packet = new Message(); packet.setTo(receiver); packet.setFrom(workgroup.getJID()); packet.setThread(thread); if (thread != null) { packet.setType(Message.Type.chat); } packet.setBody(body); send(packet); } private void send(Message packet) { InterceptorManager interceptorManager = ChatbotInterceptorManager.getInstance(); try { interceptorManager.invokeInterceptors(workgroup.getJID().toBareJID(), packet, false, false); workgroup.send(packet); interceptorManager.invokeInterceptors(workgroup.getJID().toBareJID(), packet, false, true); } catch (PacketRejectedException e) { Log.warn("Packet was not sent " + "due to interceptor REJECTION: " + packet.toXML(), e); } } public void notifyQueueStatus(JID sender, JID receiver, UserRequest request, boolean isPolling) { // Get the chatbot session of the user ChatbotSession session = getSession(receiver, false); if (session != null) { Message packet = new Message(); packet.setTo(receiver); packet.setFrom(sender); packet.setThread(session.getMessageThread()); if (session.getMessageThread() != null) { packet.setType(Message.Type.chat); } String body = getPositionMessage().replace("${position}", String.valueOf(request.getPosition() + 1)); body = body.replace("${waitTime}", String.valueOf(request.getTimeStatus())); packet.setBody(body); send(packet); } } public void notifyQueueDepartued(JID sender, JID receiver, UserRequest request, Request.CancelType type) { // Get the chatbot session of the user ChatbotSession session = getSession(receiver, false); if (session != null) { Message packet = new Message(); packet.setTo(receiver); packet.setFrom(sender); packet.setThread(session.getMessageThread()); if (session.getMessageThread() != null) { packet.setType(Message.Type.chat); } packet.setBody(getDepartureConfirmedMessage()); send(packet); // Remove the session of this user removeSession(receiver); } } public void invitationsSent(UserRequest request) { JID receiver = request.getUserJID(); // Get the chatbot session of the user ChatbotSession session = getSession(receiver, false); if (session != null) { session.setCurrentStep(4); // Send a notification saying that an invitation has been sent to start a chat // with an agent sendMessage(receiver, session.getMessageThread(), getInvitationSentMessage()); } } public void checkInvitation(UserRequest request) { JID receiver = request.getUserJID(); // Get the chatbot session of the user ChatbotSession session = getSession(receiver, false); if (session != null) { // Ask the user if he wants to receive a new invitation sendInvitationQuestion(receiver, session); } } public void supportStarted(UserRequest request) { JID receiver = request.getUserJID(); // Get the chatbot session of the user ChatbotSession session = getSession(receiver, false); if (session != null) { session.setStartedSupport(true); session.setCurrentStep(5); } } public void supportEnded(UserRequest request) { JID receiver = request.getUserJID(); // Get the chatbot session of the user ChatbotSession session = getSession(receiver, false); if (session != null) { // Set that the session's user has finished the chat with an agent session.setStartedSupport(false); // Ask the user if he wants to receive the chat transcript by email sendEmailQuestion(receiver, session); } } /** * Returns the welcome message to send to the user after the user sent his first message. * * @return the welcome message to send to the user after the user sent his first message. */ private String getWelcomeMessage() { return settings.getChatSetting(KeyEnum.welcome_message).getValue(); } /** * Returns the question to send to the user asking if he wants to join the workgroup. * * @return the question to send to the user asking if he wants to join the workgroup. */ private String getJoinQuestion() { return settings.getChatSetting(KeyEnum.join_question).getValue(); } /** * Returns the message to send if the user does not want to join the workgroup. * * @return the message to send if the user does not want to join the workgroup. */ private String getByeMessage() { return settings.getChatSetting(KeyEnum.bye_message).getValue(); } /** * Returns the message to send to the user informing that a form must be filled out. This * message is optional. * * @return the message to send to the user informing that a form must be filled out. */ private String getFilloutFormMessage() { return settings.getChatSetting(KeyEnum.fillout_form_message).getValue(); } public WorkgroupForm getForm() { return FormManager.getInstance().getWebForm(workgroup); } /** * Returns the message to inform the user that he has entered a waiting queue and * that an agent will be with him in a moment. * * @return the message to inform the user that he has entered a waiting queue and * that an agent will be with him in a moment. */ private String getRoutingMessage() { return settings.getChatSetting(KeyEnum.routing_message).getValue(); } /** * Returns the message that informs the user his position in the waiting queue. * * @return the message that informs the user his position in the waiting queue. */ private String getPositionMessage() { return settings.getChatSetting(KeyEnum.position_message).getValue(); } /** * Returns the message to send to the user when the user asks to leave the waiting queue. * * @return the message to send to the user when the user asks to leave the waiting queue. */ private String getDepartureConfirmedMessage() { return settings.getChatSetting(KeyEnum.departure_confirmed_message).getValue(); } /** * Returns the message to inform the user that the requested command cannot be processed. * * @return the message to inform the user that the requested command cannot be processed. */ private String getNotAcceptableMessage() { return settings.getChatSetting(KeyEnum.not_acceptable_message).getValue(); } /** * Returns the message to inform the user that he is not in a waiting queue yet. This message * may be sent to the user if the user sent a "position" command and the user is not in the * queue yet. * * @return the message to inform the user that he is not in a waiting queue yet. */ private String getNotInQueueMessage() { return settings.getChatSetting(KeyEnum.not_in_queue_message).getValue(); } /** * Returns the message to send to the user informing that the workgroup is currently closed. * * @return the message to send to the user informing that the workgroup is currently closed. */ private String getWorkgroupClosedMessage() { return settings.getChatSetting(KeyEnum.workgroup_closed_message).getValue(); } /** * Returns the message to send to the user informing that the user is not allowed to join * the queue. This may happen because the workgroup is closed or the specific user cannot join * due to some restriction policy. * * @return the message to send to the user informing that the user is not allowed to join * the queue. */ private String getCannotJoinMessage() { return settings.getChatSetting(KeyEnum.cannot_join_message).getValue(); } /** * Returns the message to send to the user asking if he wants to receive the chat transcript * by email. * * @return the message to send to the user asking if he wants to receive the chat transcript * by email. */ private String getSendEmailQuestion() { return settings.getChatSetting(KeyEnum.send_email_question).getValue(); } /** * Returns the message to send to the user informing that he is now being routed to an agent. * * @return the message to send to the user informing that he is now being routed to an agent. */ private String getInvitationSentMessage() { return settings.getChatSetting(KeyEnum.invitation_sent_message).getValue(); } /** * Returns the message to send to the user asking if he wants to receive again the room * invitation. * * @return the message to send to the user asking if he wants to receive again the room * invitation. */ private String getSendInvitationQuestion() { return settings.getChatSetting(KeyEnum.send_invitation_question).getValue(); } /** * Returns the message that will ask the user for his email address so that the chatbot can * send the chat transcript by email. This question will only be made if the user hasn't * entered an email address before (that means that the form is not asking for an email). * * @return the message that will ask the user for his email address so that the chatbot can * send the chat transcript by email. */ private String getGetEmailQuestion() { return settings.getChatSetting(KeyEnum.send_get_email_question).getValue(); } /** * Returns the message to send to the user informing that the invitation was sent again. * * @return the message to send to the user informing that the invitation was sent again. */ private String getInvitationResentMessage() { return settings.getChatSetting(KeyEnum.invitation_resent_message).getValue(); } /** * Returns the message to send to the user informing that the chat transcript was sent by email. * * @return the message to send to the user informing that the chat transcript was sent by email. */ private String getEmailSentMessage() { return settings.getChatSetting(KeyEnum.email_sent_message).getValue(); } /** * Returns the message that describes the effect of running the <b>back</b> command. * * @return the message that describes the effect of running the <b>back</b> command. */ private String getBackHelpMessage() { return settings.getChatSetting(KeyEnum.back_help_message).getValue(); } /** * Returns the message that describes the effect of running the <b>repeat</b> command. * * @return the message that describes the effect of running the <b>repeat</b> command. */ private String getRepeatHelpMessage() { return settings.getChatSetting(KeyEnum.repeat_help_message).getValue(); } /** * Returns the message that describes the effect of running the <b>help</b> command. * * @return the message that describes the effect of running the <b>help</b> command. */ private String getHelpHelpMessage() { return settings.getChatSetting(KeyEnum.help_help_message).getValue(); } /** * Returns the message that describes the effect of running the <b>bye</b> command. * * @return the message that describes the effect of running the <b>bye</b> command. */ private String getByeHelpMessage() { return settings.getChatSetting(KeyEnum.bye_help_message).getValue(); } /** * Returns the message that describes the effect of running the <b>position</b> command. * * @return the message that describes the effect of running the <b>position</b> command. */ private String getPositionHelpMessage() { return settings.getChatSetting(KeyEnum.position_help_message).getValue(); } /** * Returns the string that indicates that the user is requesting to go back one step in * the dialog and repeat the previous message. * * @return the string that indicates that the user is requesting to go back one step in * the dialog and repeat the previous message. */ private String getBackCommand() { return settings.getChatSetting(KeyEnum.back_command).getValue(); } /** * Returns the string that indicates that the user is requesting to repeat the last message. * This may happen if for instance the user closed his chat window by mistake. * * @return the string that indicates that the user is requesting to repeat the last message. */ private String getRepeatCommand() { return settings.getChatSetting(KeyEnum.repeat_command).getValue(); } /** * Returns the string that indicates that the user is requesting the list of available commands. * * @return the string that indicates that the user is requesting the list of available commands. */ private String getHelpCommand() { return settings.getChatSetting(KeyEnum.help_command).getValue(); } /** * Returns the string that indicates that the user is closing the chat session no matter * the if he already joined a waiting queue or not. If the user joined a waiting queue then * the user will leave the queue. * * @return the string that indicates that the user is closing the chat session no matter * the if he already joined a waiting queue or not. */ private String getByeCommand() { return settings.getChatSetting(KeyEnum.bye_command).getValue(); } /** * Returns the string that indicates that the user wants to know his position in the * waiting queue. If the user is not in the waiting queue then send the "notInQueueMessage" * message. * * @return the string that indicates that the user wants to know his position in the * waiting queue. */ private String getPositionCommand() { return settings.getChatSetting(KeyEnum.position_command).getValue(); } /** * Removes idle sessions. After a session has been removed, if a user sends a message to the * chatbot then the script will start from scratch. Therefore, all the stored information * in the session will be lost. */ public void cleanup() { final long deadline = System.currentTimeMillis() - getIdleTimeout(); for (ChatbotSession session : sessions.values()) { // Do not clean up sessions whose users are having a chat with an agent. if (!session.isStartedSupport() && session.getLastActiveDate().getTime() < deadline) { Log.debug("Removing idle chat " + "session for: " + session.getUserJID()); removeSession(session.getUserJID()); } } } /** * Sets the time to wait before considering an idle session candidate to be removed. * * @param timeout the number of milliseconds that a session must be idle. */ public void setIdleTimeout(long timeout) { try { workgroup.getProperties().setProperty("chatbot.session.timeout", String.valueOf(timeout)); } catch (UnauthorizedException e) { Log.error("Error setting timeout", e); } } /** * Returns the milliseconds that a session must be idle to be considered candidate for removal. * * @return the milliseconds that a session must be idle to be considered candidate for removal. */ public long getIdleTimeout() { long timeout = 30 * 60 * 1000; try { timeout =Long.parseLong(workgroup.getProperties().getProperty( "chatbot.session.timeout")); } catch (NumberFormatException e) { // Ignore. } return timeout; } }