mirror of
https://github.com/chatopera/cosin.git
synced 2025-06-16 18:30:03 +08:00
https://github.com/chatopera/cskefu/issues/732 add Facebook Messenger Plugin
This commit is contained in:
parent
6facf37fb0
commit
25ff3a2052
4
contact-center/app/.gitignore
vendored
4
contact-center/app/.gitignore
vendored
@ -1,10 +1,6 @@
|
||||
# dev profile
|
||||
src/main/resources/application-dev.properties
|
||||
|
||||
# ignore channel views within plugins
|
||||
src/main/resources/templates/admin/channel/*
|
||||
!src/main/resources/templates/admin/channel/im
|
||||
|
||||
# ignore app views within plugins
|
||||
src/main/resources/templates/apps/callout
|
||||
src/main/resources/templates/apps/callcenter
|
||||
|
@ -1,3 +0,0 @@
|
||||
plugins/*
|
||||
!plugins/chatbot
|
||||
!plugins/README.md
|
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* 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 com.chatopera.cc.plugins.messenger;
|
||||
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.controller.Handler;
|
||||
import com.chatopera.cc.model.FbMessenger;
|
||||
import com.chatopera.cc.model.Organ;
|
||||
import com.chatopera.cc.model.SNSAccount;
|
||||
import com.chatopera.cc.persistence.repository.FbMessengerRepository;
|
||||
import com.chatopera.cc.persistence.repository.OrganRepository;
|
||||
import com.chatopera.cc.persistence.repository.SNSAccountRepository;
|
||||
import com.chatopera.cc.proxy.OrganProxy;
|
||||
import com.chatopera.cc.util.Menu;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.validation.Valid;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/admin/messenger")
|
||||
public class MessengerChannelController extends Handler {
|
||||
private final static Logger logger = LoggerFactory.getLogger(MessengerChannelController.class);
|
||||
|
||||
@Autowired
|
||||
private FbMessengerRepository fbMessengerRepository;
|
||||
|
||||
@Autowired
|
||||
private OrganRepository organRepository;
|
||||
|
||||
@Autowired
|
||||
private OrganProxy organProxy;
|
||||
|
||||
@Autowired
|
||||
private SNSAccountRepository snsAccountRepository;
|
||||
|
||||
private Map<String, Organ> getOwnOrgan(HttpServletRequest request) {
|
||||
return organProxy.findAllOrganByParentAndOrgi(super.getOrgan(request), super.getOrgi(request));
|
||||
|
||||
}
|
||||
|
||||
@RequestMapping("/index")
|
||||
@Menu(type = "admin", subtype = "messenger")
|
||||
public ModelAndView index(ModelMap map, HttpServletRequest request) {
|
||||
Map<String, Organ> organs = getOwnOrgan(request);
|
||||
List<FbMessenger> fbMessengers = fbMessengerRepository.findByOrganIn(organs.keySet());
|
||||
Organ currentOrgan = super.getOrgan(request);
|
||||
map.addAttribute("fbMessengers", fbMessengers);
|
||||
map.addAttribute("organs", organs);
|
||||
map.addAttribute("organ", currentOrgan);
|
||||
return request(super.createView("/admin/channel/messenger/index"));
|
||||
}
|
||||
|
||||
@RequestMapping("/add")
|
||||
@Menu(type = "admin", subtype = "messenger")
|
||||
public ModelAndView add(ModelMap map, HttpServletRequest request) {
|
||||
Organ currentOrgan = super.getOrgan(request);
|
||||
map.addAttribute("organ", currentOrgan);
|
||||
return request(super.createView("/admin/channel/messenger/add"));
|
||||
}
|
||||
|
||||
@RequestMapping("/save")
|
||||
@Menu(type = "admin", subtype = "messenger")
|
||||
public ModelAndView save(ModelMap map, HttpServletRequest request, @Valid FbMessenger fbMessenger) {
|
||||
String msg = "save_ok";
|
||||
Organ currentOrgan = super.getOrgan(request);
|
||||
FbMessenger fbMessengerOne = fbMessengerRepository.findOneByPageId(fbMessenger.getPageId());
|
||||
if (fbMessengerOne != null) {
|
||||
msg = "save_no_PageId";
|
||||
} else {
|
||||
fbMessenger.setId(MainUtils.getUUID());
|
||||
fbMessenger.setOrgan(currentOrgan.getId());
|
||||
|
||||
if (fbMessenger.getStatus() == null) {
|
||||
fbMessenger.setStatus("disabled");
|
||||
}
|
||||
fbMessenger.setCreatetime(new Date());
|
||||
fbMessenger.setUpdatetime(new Date());
|
||||
fbMessenger.setAiid(null);
|
||||
fbMessengerRepository.save(fbMessenger);
|
||||
|
||||
SNSAccount snsAccount = new SNSAccount();
|
||||
snsAccount.setId(MainUtils.genID());
|
||||
snsAccount.setCreatetime(new Date());
|
||||
snsAccount.setOrgi(super.getOrgi(request));
|
||||
snsAccount.setName(fbMessenger.getName());
|
||||
snsAccount.setOrgan(currentOrgan.getId());
|
||||
snsAccount.setSnsid(fbMessenger.getPageId());
|
||||
snsAccount.setSnstype(MainContext.ChannelType.MESSENGER.toString());
|
||||
snsAccountRepository.save(snsAccount);
|
||||
}
|
||||
return request(super.createView("redirect:/admin/messenger/index.html?msg=" + msg));
|
||||
}
|
||||
|
||||
@RequestMapping("/edit")
|
||||
@Menu(type = "admin", subtype = "messenger")
|
||||
public ModelAndView edit(ModelMap map, HttpServletRequest request, @Valid String id) {
|
||||
FbMessenger fbMessenger = fbMessengerRepository.findOne(id);
|
||||
|
||||
Organ fbOrgan = organRepository.getOne(fbMessenger.getOrgan());
|
||||
map.addAttribute("organ", fbOrgan);
|
||||
map.addAttribute("fb", fbMessenger);
|
||||
|
||||
return request(super.createView("/admin/channel/messenger/edit"));
|
||||
}
|
||||
|
||||
@RequestMapping("/update")
|
||||
@Menu(type = "admin", subtype = "messenger")
|
||||
public ModelAndView update(ModelMap map, HttpServletRequest request, @Valid FbMessenger fbMessenger) {
|
||||
String msg = "update_ok";
|
||||
FbMessenger oldMessenger = fbMessengerRepository.findOne(fbMessenger.getId());
|
||||
oldMessenger.setName(fbMessenger.getName());
|
||||
if (fbMessenger.getStatus() != null) {
|
||||
oldMessenger.setStatus(fbMessenger.getStatus());
|
||||
} else {
|
||||
oldMessenger.setStatus(Constants.MESSENGER_CHANNEL_DISABLED);
|
||||
}
|
||||
|
||||
oldMessenger.setToken(fbMessenger.getToken());
|
||||
oldMessenger.setVerifyToken(fbMessenger.getVerifyToken());
|
||||
oldMessenger.setUpdatetime(new Date());
|
||||
|
||||
fbMessengerRepository.save(oldMessenger);
|
||||
|
||||
return request(super.createView("redirect:/admin/messenger/index.html?msg=" + msg));
|
||||
}
|
||||
|
||||
@RequestMapping("/delete")
|
||||
@Menu(type = "admin", subtype = "messenger")
|
||||
public ModelAndView delete(ModelMap map, HttpServletRequest request, @Valid String id) {
|
||||
String msg = "delete_ok";
|
||||
FbMessenger fbMessenger = fbMessengerRepository.getOne(id);
|
||||
fbMessengerRepository.delete(id);
|
||||
|
||||
snsAccountRepository.findBySnsid(fbMessenger.getPageId()).ifPresent(snsAccount -> {
|
||||
snsAccountRepository.delete(snsAccount);
|
||||
});
|
||||
|
||||
return request(super.createView("redirect:/admin/messenger/index.html?msg=" + msg));
|
||||
}
|
||||
|
||||
@RequestMapping("/setting")
|
||||
@Menu(type = "admin", subtype = "messenger")
|
||||
public ModelAndView setting(ModelMap map, HttpServletRequest request, @Valid String id) {
|
||||
FbMessenger fbMessenger = fbMessengerRepository.findOne(id);
|
||||
Organ fbOrgan = organRepository.getOne(fbMessenger.getOrgan());
|
||||
|
||||
map.mergeAttributes(fbMessenger.parseConfigMap());
|
||||
map.addAttribute("organ", fbOrgan);
|
||||
map.addAttribute("fb", fbMessenger);
|
||||
|
||||
return request(super.createView("/admin/channel/messenger/setting"));
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/setting/save", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
@Menu(type = "admin", subtype = "messenger")
|
||||
public ModelAndView saveSetting(ModelMap map, HttpServletRequest request, @Valid String id, @RequestBody MultiValueMap<String, String> formData) {
|
||||
String msg = "update_ok";
|
||||
|
||||
FbMessenger fbMessenger = fbMessengerRepository.findOne(id);
|
||||
if (fbMessenger != null) {
|
||||
fbMessenger.setConfigMap(formData.toSingleValueMap());
|
||||
fbMessengerRepository.save(fbMessenger);
|
||||
}
|
||||
|
||||
return request(super.createView("redirect:/admin/messenger/index.html?msg=" + msg));
|
||||
}
|
||||
|
||||
@RequestMapping("/setStatus")
|
||||
@Menu(type = "admin", subtype = "messenger")
|
||||
@ResponseBody
|
||||
public String setStatus(ModelMap map, HttpServletRequest request, @Valid String id, @Valid String status) {
|
||||
FbMessenger fbMessenger = fbMessengerRepository.findOne(id);
|
||||
fbMessenger.setStatus(status);
|
||||
fbMessengerRepository.save(fbMessenger);
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,90 @@
|
||||
package com.chatopera.cc.plugins.messenger;
|
||||
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.model.AgentUser;
|
||||
import com.chatopera.cc.model.OnlineUser;
|
||||
import com.chatopera.cc.peer.PeerContext;
|
||||
import com.chatopera.cc.persistence.repository.OnlineUserRepository;
|
||||
import com.chatopera.cc.socketio.message.ChatMessage;
|
||||
import com.chatopera.compose4j.Functional;
|
||||
import com.chatopera.compose4j.Middleware;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Messenger 消息处理
|
||||
*/
|
||||
@Component
|
||||
public class MessengerChannelMessager implements Middleware<PeerContext> {
|
||||
private final static Logger logger = LoggerFactory.getLogger(MessengerChannelMessager.class);
|
||||
|
||||
@Autowired
|
||||
private OnlineUserRepository onlineUserRes;
|
||||
|
||||
@Autowired
|
||||
private MessengerMessageProxy messengerMessageProxy;
|
||||
|
||||
@Override
|
||||
public void apply(final PeerContext ctx, final Functional next) {
|
||||
if ((!ctx.isSent()) && ctx.getChannel() == MainContext.ChannelType.MESSENGER) {
|
||||
AgentUser agentUser = ctx.getMessage().getAgentUser();
|
||||
|
||||
final OnlineUser onlineUser = onlineUserRes.findOneByUseridAndOrgi(
|
||||
agentUser.getUserid(), agentUser.getOrgi());
|
||||
|
||||
if (onlineUser != null) {
|
||||
handle(ctx, onlineUser);
|
||||
} else {
|
||||
logger.info(
|
||||
"[apply] can not online user or its contactsid with agentUserId {}, userid {} and orgi {}",
|
||||
agentUser.getId(), agentUser.getUserid(), agentUser.getOrgi());
|
||||
}
|
||||
}
|
||||
next.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理消息体
|
||||
*
|
||||
* @param ctx
|
||||
* @param onlineUser
|
||||
*/
|
||||
private void handle(final PeerContext ctx, final OnlineUser onlineUser) {
|
||||
if (ctx.getMessage().getChannelMessage() instanceof ChatMessage) {
|
||||
final ChatMessage chatMessage = (ChatMessage) ctx.getMessage().getChannelMessage();
|
||||
logger.info(
|
||||
"[apply] chat message type {}, content {}", chatMessage.getMsgtype(),
|
||||
chatMessage.getMessage());
|
||||
if (StringUtils.equals(chatMessage.getMsgtype(), MainContext.MediaType.TEXT.toString())) {
|
||||
Document document = Jsoup.parse(chatMessage.getMessage());
|
||||
Elements pngs = document.select("img[src]");
|
||||
if (pngs.size() > 0) {
|
||||
for (Element element : pngs) {
|
||||
String imgUrl = element.attr("src");
|
||||
if (StringUtils.isNotBlank(imgUrl)) {
|
||||
messengerMessageProxy.sendImage(onlineUser.getAppid(), onlineUser.getUserid(), imgUrl);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
messengerMessageProxy.send(onlineUser.getAppid(), onlineUser.getUserid(), document.text());
|
||||
}
|
||||
} else if (StringUtils.equals(chatMessage.getMsgtype(), MainContext.MediaType.IMAGE.toString())) {
|
||||
messengerMessageProxy.sendImage(onlineUser.getAppid(), onlineUser.getUserid(), chatMessage.getMessage());
|
||||
}
|
||||
logger.info("[apply] message is sent.");
|
||||
ctx.setSent(true);
|
||||
} else {
|
||||
if (StringUtils.isNotBlank(ctx.getMessage().getMessage())) {
|
||||
messengerMessageProxy.send(onlineUser.getAppid(), onlineUser.getUserid(), ctx.getMessage().getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
package com.chatopera.cc.plugins.messenger;
|
||||
|
||||
import com.chatopera.bot.exception.ChatbotException;
|
||||
import com.chatopera.cc.acd.ACDServiceRouter;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.exception.CSKefuException;
|
||||
import com.chatopera.cc.model.*;
|
||||
import com.chatopera.cc.persistence.repository.*;
|
||||
import com.chatopera.cc.plugins.chatbot.ChatbotProxy;
|
||||
import com.chatopera.cc.proxy.OnlineUserProxy;
|
||||
import com.chatopera.cc.socketio.message.ChatMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
public class MessengerChatbot {
|
||||
private final static Logger logger = LoggerFactory.getLogger(MessengerChatbot.class);
|
||||
|
||||
@Autowired
|
||||
private ChatbotProxy chatbotProxy;
|
||||
|
||||
@Autowired
|
||||
private AgentUserRepository agentUserRes;
|
||||
|
||||
@Autowired
|
||||
private ChatMessageRepository chatMessageRes;
|
||||
|
||||
@Autowired
|
||||
private SNSAccountRepository snsAccountRes;
|
||||
|
||||
@Autowired
|
||||
private AgentServiceRepository agentServiceRes;
|
||||
|
||||
@Autowired
|
||||
private MessengerMessageProxy messengerMessageProxy;
|
||||
|
||||
@Autowired
|
||||
private OnlineUserRepository onlineUserRes;
|
||||
|
||||
public boolean receiveMessage(String chatbotId, String fromId, String snsID, OnlineUser onlineUser, MainContext.MediaType msgType, String msg) throws ChatbotException, MalformedURLException {
|
||||
logger.info("[receiveMessage] message: chatbotId {},fromId {},toId {},msg {} ", chatbotId, fromId, snsID, msg);
|
||||
// 在线客服访客咨询记录
|
||||
Date now = new Date();
|
||||
Optional<SNSAccount> optionalSNSAccount = snsAccountRes.findBySnsid(snsID);
|
||||
SNSAccount snsAccount = optionalSNSAccount.get();
|
||||
CousultInvite invite = OnlineUserProxy.consult(onlineUser.getAppid(), Constants.SYSTEM_ORGI);
|
||||
AgentUser agentUser = agentUserRes.findOneByUseridAndStatusNotAndChannelAndOrgi(fromId, MainContext.AgentUserStatusEnum.END.toString(), MainContext.ChannelType.MESSENGER.toString(), Constants.SYSTEM_ORGI).orElseGet(() -> {
|
||||
AgentUser au = new AgentUser(
|
||||
onlineUser.getUserid(),
|
||||
MainContext.ChannelType.MESSENGER.toString(),
|
||||
onlineUser.getId(),
|
||||
onlineUser.getUsername(),
|
||||
Constants.SYSTEM_ORGI,
|
||||
onlineUser.getAppid());
|
||||
|
||||
au.setServicetime(now);
|
||||
au.setCreatetime(now);
|
||||
au.setUpdatetime(now);
|
||||
au.setLogindate(now);
|
||||
au.setSessionid(onlineUser.getSessionid());
|
||||
au.setRegion(onlineUser.getRegion());
|
||||
au.setUsername(onlineUser.getUsername());
|
||||
au.setSkill(snsAccount.getOrgan());
|
||||
au.setAppid(snsAccount.getSnsid());
|
||||
au.setOrgi(Constants.SYSTEM_ORGI);
|
||||
au.setNickname(onlineUser.getUsername());
|
||||
au.setStatus(MainContext.AgentUserStatusEnum.INSERVICE.toString());
|
||||
|
||||
// 聊天机器人处理的请求
|
||||
au.setOpttype(MainContext.OptType.CHATBOT.toString());
|
||||
au.setAgentno(chatbotId); // 聊天机器人ID
|
||||
au.setAgentname(invite != null ? invite.getAiname() : "机器人客服");
|
||||
au.setCity(onlineUser.getCity());
|
||||
au.setProvince(onlineUser.getProvince());
|
||||
au.setCountry(onlineUser.getCountry());
|
||||
AgentService agentService = ACDServiceRouter.getAcdChatbotService().processChatbotService(
|
||||
invite != null ? invite.getAiname() : "机器人客服", au, Constants.SYSTEM_ORGI);
|
||||
au.setAgentserviceid(agentService.getId());
|
||||
// 标记为机器人坐席
|
||||
au.setChatbotops(true);
|
||||
// 保存到MySQL
|
||||
agentUserRes.save(au);
|
||||
|
||||
return au;
|
||||
});
|
||||
|
||||
if (agentUser.isChatbotops()) {
|
||||
ChatMessage data = new ChatMessage();
|
||||
data.setMessage(msg);
|
||||
data.setUserid(fromId);
|
||||
data.setUsession(fromId); // 绑定唯一用户
|
||||
data.setSessionid(agentUser.getSessionid());
|
||||
data.setMessage(MainUtils.processEmoti(data.getMessage())); // 处理表情
|
||||
data.setTouser(chatbotId);
|
||||
data.setUsername(agentUser.getUsername());
|
||||
data.setAiid(chatbotId);
|
||||
data.setAgentserviceid(agentUser.getAgentserviceid());
|
||||
data.setChannel(agentUser.getChannel());
|
||||
data.setOrgi(Constants.SYSTEM_ORGI);
|
||||
data.setContextid(agentUser.getAgentserviceid()); // 一定要设置 ContextID
|
||||
data.setCalltype(MainContext.CallType.IN.toString());
|
||||
data.setMsgtype(msgType.toString());
|
||||
|
||||
chatMessageRes.save(data);
|
||||
|
||||
if (MainContext.MediaType.TEXT == msgType) {
|
||||
// 发送消息给Bot
|
||||
chatbotProxy.publishMessage(data, Constants.CHATBOT_EVENT_TYPE_CHAT);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void switchManualCustomerService(String fromId) {
|
||||
agentUserRes.findOneByUseridAndStatusNotAndChannelAndOrgi(fromId, MainContext.AgentUserStatusEnum.END.toString(), MainContext.ChannelType.MESSENGER.toString(), Constants.SYSTEM_ORGI).ifPresent(agentUser -> {
|
||||
if (agentUser.isChatbotops()) {
|
||||
Date now = new Date();
|
||||
AgentService agentService = agentServiceRes.findOne(agentUser.getAgentserviceid());
|
||||
agentService.setStatus(MainContext.AgentUserStatusEnum.END.toString());
|
||||
agentService.setEndtime(now);
|
||||
agentServiceRes.save(agentService);
|
||||
agentUser.setAgentserviceid(null);
|
||||
agentUser.setChatbotops(false);
|
||||
agentUser.setAgentno(null);
|
||||
agentUserRes.save(agentUser);
|
||||
|
||||
snsAccountRes.findBySnsid(agentUser.getAppid()).ifPresent(snsAccount -> {
|
||||
OnlineUser onlineUser = onlineUserRes.findOneByUseridAndOrgi(fromId, Constants.SYSTEM_ORGI);
|
||||
try {
|
||||
messengerMessageProxy.scheduleMessengerAgentUser(agentUser, onlineUser, snsAccount, Constants.SYSTEM_ORGI);
|
||||
} catch (CSKefuException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package com.chatopera.cc.plugins.messenger;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.model.FbMessenger;
|
||||
import com.chatopera.cc.model.OnlineUser;
|
||||
import com.chatopera.cc.persistence.repository.FbMessengerRepository;
|
||||
import com.chatopera.cc.persistence.repository.OnlineUserRepository;
|
||||
import com.chatopera.cc.plugins.chatbot.ChatbotConstants;
|
||||
import com.chatopera.cc.plugins.chatbot.ChatbotContext;
|
||||
import com.chatopera.cc.socketio.message.ChatMessage;
|
||||
import com.chatopera.compose4j.Functional;
|
||||
import com.chatopera.compose4j.Middleware;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Component
|
||||
public class MessengerChatbotMessager implements Middleware<ChatbotContext> {
|
||||
private final static Logger logger = LoggerFactory.getLogger(MessengerChatbotMessager.class);
|
||||
|
||||
private final static String EVALUATION_YES_REPLY = "evaluationYesReply";
|
||||
private final static String EVALUATION_NO_REPLY = "evaluationNoReply";
|
||||
|
||||
@Autowired
|
||||
private MessengerMessageProxy messengerMessageProxy;
|
||||
|
||||
@Autowired
|
||||
private OnlineUserRepository onlineUserRes;
|
||||
|
||||
@Autowired
|
||||
private FbMessengerRepository fbMessengerRepository;
|
||||
|
||||
private final Map<String, String> messengerConfig = new HashMap<String, String>() {
|
||||
{
|
||||
put("transferManualService", "转人工");
|
||||
put("suggestQuestion", "您是否想问以下问题");
|
||||
put("evaluationAsk", "以上答案是否对您有帮助");
|
||||
put("evaluationYes", "是");
|
||||
put("evaluationNo", "否");
|
||||
put(EVALUATION_YES_REPLY, "感谢您的反馈,我们会做的更好!");
|
||||
put(EVALUATION_NO_REPLY, "感谢您的反馈,机器人在不断的学习!");
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void apply(final ChatbotContext ctx, final Functional next) {
|
||||
ChatMessage resp = ctx.getResp();
|
||||
if (MainContext.ChannelType.MESSENGER.toString().equals(resp.getChannel())) {
|
||||
|
||||
final OnlineUser onlineUser = onlineUserRes.findOneByUseridAndOrgi(
|
||||
resp.getUserid(), Constants.SYSTEM_ORGI);
|
||||
|
||||
Map<String, String> configMap = messengerConfig;
|
||||
FbMessenger fbMessenger = fbMessengerRepository.findOneByPageId(onlineUser.getAppid());
|
||||
if (fbMessenger != null && StringUtils.isNotBlank(fbMessenger.getConfig())) {
|
||||
configMap = (Map<String, String>) JSONObject.parse(fbMessenger.getConfig());
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(resp.getExpmsg())) {
|
||||
String jsonStr = processGenericTemplate(resp.getExpmsg(), configMap);
|
||||
messengerMessageProxy.send(onlineUser.getAppid(), onlineUser.getUserid(), JSONObject.parseObject(jsonStr), fbMessenger);
|
||||
} else {
|
||||
messengerMessageProxy.send(onlineUser.getAppid(), onlineUser.getUserid(), processTextTemplate(resp.getMessage(), configMap));
|
||||
}
|
||||
}
|
||||
next.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换文本消息
|
||||
*
|
||||
* @param template
|
||||
* @param params
|
||||
* @return
|
||||
*/
|
||||
public static String processTextTemplate(String template, Map<String, String> params) {
|
||||
if (StringUtils.equals(template, ChatbotConstants.PROVIDER_FEEDBACK_EVAL_POSITIVE_REPLY_PLACEHOLDER)) {
|
||||
if (params.containsKey(EVALUATION_YES_REPLY) && StringUtils.isNotBlank(params.get(EVALUATION_YES_REPLY))) {
|
||||
return params.get(EVALUATION_YES_REPLY);
|
||||
}
|
||||
} else if (StringUtils.equals(template, ChatbotConstants.PROVIDER_FEEDBACK_EVAL_NEGATIVE_REPLY_PLACEHOLDER)) {
|
||||
if (params.containsKey(EVALUATION_NO_REPLY) && StringUtils.isNotBlank(params.get(EVALUATION_NO_REPLY))) {
|
||||
return params.get(EVALUATION_NO_REPLY);
|
||||
}
|
||||
} else if (StringUtils.equals(template, "${leaveMeAlone}")) {
|
||||
return "";
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换模版消息
|
||||
*
|
||||
* @param template
|
||||
* @param params
|
||||
* @return
|
||||
*/
|
||||
public static String processGenericTemplate(String template, Map<String, String> params) {
|
||||
Matcher m = Pattern.compile("\\$\\{\\w+\\}").matcher(template);
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
while (m.find()) {
|
||||
String param = m.group();
|
||||
Object value = params.get(param.substring(2, param.length() - 1));
|
||||
m.appendReplacement(sb, value == null ? "" : value.toString());
|
||||
}
|
||||
m.appendTail(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package com.chatopera.cc.plugins.messenger;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.model.FbMessenger;
|
||||
import com.chatopera.cc.model.FbOTN;
|
||||
import com.chatopera.cc.model.FbOtnFollow;
|
||||
import com.chatopera.cc.persistence.repository.FbMessengerRepository;
|
||||
import com.chatopera.cc.persistence.repository.FbOTNFollowRepository;
|
||||
import com.chatopera.cc.persistence.repository.FbOTNRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jms.annotation.JmsListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class MessengerEventSubscription {
|
||||
private final static Logger logger = LoggerFactory.getLogger(MessengerEventSubscription.class);
|
||||
|
||||
@Autowired
|
||||
private FbMessengerRepository fbMessengerRepository;
|
||||
|
||||
@Autowired
|
||||
private FbOTNRepository otnRepository;
|
||||
|
||||
@Autowired
|
||||
private FbOTNFollowRepository otnFollowRepository;
|
||||
|
||||
@Autowired
|
||||
private MessengerMessageProxy messengerMessageProxy;
|
||||
|
||||
@JmsListener(destination = Constants.INSTANT_MESSAGING_MQ_QUEUE_FACEBOOK_OTN, containerFactory = "jmsListenerContainerQueue")
|
||||
public void onPublish(final String jsonStr) {
|
||||
JSONObject payload = JSONObject.parseObject(jsonStr);
|
||||
String otnId = payload.getString("otnId");
|
||||
Date sendtime = payload.getTimestamp("sendtime");
|
||||
|
||||
FbOTN otn = otnRepository.getOne(otnId);
|
||||
FbMessenger fbMessenger = fbMessengerRepository.findOneByPageId(otn.getPageId());
|
||||
if (fbMessenger != null && otn != null) {
|
||||
if (otn.getStatus().equals("create") && otn.getSendtime() != null && otn.getSendtime().equals(sendtime)) {
|
||||
otn.setStatus("sending");
|
||||
otnRepository.save(otn);
|
||||
}
|
||||
|
||||
if (otn.getStatus().equals("sending")) {
|
||||
List<FbOtnFollow> follows = otnFollowRepository.findByOtnId(otn.getId());
|
||||
for (FbOtnFollow f : follows) {
|
||||
if (f.getSendtime() == null) {
|
||||
messengerMessageProxy.sendOtnText(fbMessenger.getToken(), f.getOtnToken(), otn.getOtnMessage());
|
||||
f.setSendtime(new Date());
|
||||
otnFollowRepository.save(f);
|
||||
}
|
||||
}
|
||||
|
||||
otn.setStatus("finish");
|
||||
otnRepository.save(otn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,540 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* 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 com.chatopera.cc.plugins.messenger;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.chatopera.bot.exception.ChatbotException;
|
||||
import com.chatopera.cc.acd.ACDAgentService;
|
||||
import com.chatopera.cc.acd.ACDVisitorDispatcher;
|
||||
import com.chatopera.cc.acd.basic.ACDComposeContext;
|
||||
import com.chatopera.cc.acd.basic.ACDMessageHelper;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.exception.CSKefuException;
|
||||
import com.chatopera.cc.model.*;
|
||||
import com.chatopera.cc.peer.PeerSyncIM;
|
||||
import com.chatopera.cc.persistence.blob.JpaBlobHelper;
|
||||
import com.chatopera.cc.persistence.repository.*;
|
||||
import com.chatopera.cc.socketio.message.ChatMessage;
|
||||
import com.chatopera.cc.socketio.message.Message;
|
||||
import com.chatopera.cc.util.HttpClientUtil;
|
||||
import org.apache.commons.lang.StringEscapeUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
public class MessengerMessageProxy {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(MessengerMessageProxy.class);
|
||||
|
||||
private final String FACEBOOK_MESSAGES_API = "https://graph.facebook.com/v2.6/me/messages";
|
||||
|
||||
@Autowired
|
||||
private OnlineUserRepository onlineUserRes;
|
||||
|
||||
@Autowired
|
||||
private AgentUserRepository agentUserRes;
|
||||
|
||||
@Autowired
|
||||
private SNSAccountRepository snsAccountRes;
|
||||
|
||||
@Autowired
|
||||
private ACDMessageHelper acdMessageHelper;
|
||||
|
||||
@Autowired
|
||||
private ACDVisitorDispatcher acdVisitorDispatcher;
|
||||
|
||||
@Autowired
|
||||
private AgentServiceRepository agentServiceRes;
|
||||
|
||||
@Autowired
|
||||
private Cache cache;
|
||||
|
||||
@Autowired
|
||||
private PeerSyncIM peerSyncIM;
|
||||
|
||||
@Autowired
|
||||
private FbMessengerRepository fbMessengerRepository;
|
||||
|
||||
@Autowired
|
||||
private ChatbotRepository chatbotRes;
|
||||
|
||||
@Autowired
|
||||
private MessengerChatbot messengerChatbot;
|
||||
|
||||
@Autowired
|
||||
private FbOTNRepository otnRepository;
|
||||
|
||||
@Autowired
|
||||
private FbOTNFollowRepository otnFollowRepository;
|
||||
|
||||
@Autowired
|
||||
private ACDAgentService acdAgentService;
|
||||
|
||||
@Autowired
|
||||
private ChatMessageRepository chatMessageRes;
|
||||
|
||||
@Autowired
|
||||
private JpaBlobHelper jpaBlobHelper;
|
||||
|
||||
@Autowired
|
||||
private StreamingFileRepository streamingFileRes;
|
||||
|
||||
@Value("${uk.im.server.host}")
|
||||
private String host;
|
||||
|
||||
public void acceptOTNReq(String fromId, String toId, String otnToken, String ref) {
|
||||
FbOTN otn = otnRepository.findOne(ref);
|
||||
if (otn != null) {
|
||||
FbOtnFollow follow = new FbOtnFollow();
|
||||
follow.setId(MainUtils.getUUID());
|
||||
follow.setOtnId(ref);
|
||||
follow.setUserId(fromId);
|
||||
follow.setPageId(toId);
|
||||
follow.setOtnToken(otnToken);
|
||||
follow.setCreatetime(new Date());
|
||||
follow.setUpdatetime(new Date());
|
||||
|
||||
otnFollowRepository.save(follow);
|
||||
|
||||
otnRepository.incOneSubNumById(otn.getId());
|
||||
|
||||
sendOTNContent(toId, fromId, JSONObject.parseObject(otn.getSuccessMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
public void acceptMeLink(String fromId, String toId, String ref) {
|
||||
FbOTN otn = otnRepository.findOne(ref);
|
||||
if (otn != null) {
|
||||
if (StringUtils.isNotBlank(otn.getPreSubMessage())) {
|
||||
Object obj = JSON.parse(otn.getPreSubMessage());
|
||||
if (obj instanceof JSONObject) {
|
||||
JSONObject json = (JSONObject) obj;
|
||||
sendOTNContent(toId, fromId, json);
|
||||
} else if (obj instanceof JSONArray) {
|
||||
JSONArray jsonArray = (JSONArray) obj;
|
||||
sendOTNContent(toId, fromId, jsonArray.getJSONObject(0));
|
||||
sendOTNContent(toId, fromId, jsonArray.getJSONObject(1));
|
||||
}
|
||||
otnRepository.incOneMelinkNumById(otn.getId());
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(otn.getSubMessage())) {
|
||||
sendOTNReq(toId, fromId, otn.getSubMessage(), ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void accept(String fromId, String toId, MainContext.MediaType msgType, String msg) throws CSKefuException, ChatbotException, MalformedURLException {
|
||||
Optional<SNSAccount> optionalSNSAccount = snsAccountRes.findBySnsid(toId);
|
||||
|
||||
if (!optionalSNSAccount.isPresent()) {
|
||||
logger.warn("[handle] SnsAccount is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
SNSAccount snsAccount = optionalSNSAccount.get();
|
||||
|
||||
Date now = new Date();
|
||||
|
||||
OnlineUser onlineUser = onlineUserRes.findOneByUseridAndOrgi(fromId, Constants.SYSTEM_ORGI);
|
||||
if (onlineUser == null) {
|
||||
onlineUser = new OnlineUser();
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(onlineUser.getUserid())) {
|
||||
Map<String, String> profile = getPersonName(toId, fromId);
|
||||
|
||||
// 创建新的Onlineuser
|
||||
onlineUser.setId(MainUtils.getUUID());
|
||||
onlineUser.setUpdatetime(now);
|
||||
onlineUser.setUsername(profile.get("name"));
|
||||
onlineUser.setHeadimgurl(profile.get("profile_pic"));
|
||||
onlineUser.setCreatetime(now);
|
||||
onlineUser.setLogintime(now);
|
||||
onlineUser.setChannel(MainContext.ChannelType.MESSENGER.toString());
|
||||
onlineUser.setAppid(snsAccount.getSnsid());
|
||||
onlineUser.setStatus(MainContext.OnlineUserStatusEnum.ONLINE.toString());
|
||||
onlineUser.setOrgi(Constants.SYSTEM_ORGI);
|
||||
onlineUser.setUserid(fromId);
|
||||
onlineUser.setUsertype(MainContext.OnlineUserType.MESSENGER.toString());
|
||||
onlineUserRes.save(onlineUser);
|
||||
} else if (cache.existBlackEntityByUserIdAndOrgi(onlineUser.getUserid(), Constants.SYSTEM_ORGI)) {
|
||||
// 检查该访客是否被拉黑
|
||||
logger.info("[handle] online user {} is in black list.", onlineUser.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
Chatbot c = chatbotRes.findBySnsAccountIdentifierAndOrgi(toId, Constants.SYSTEM_ORGI);
|
||||
if (c != null && c.isEnabled()) {
|
||||
|
||||
if (!StringUtils.equals(Constants.CHATBOT_HUMAN_FIRST, c.getWorkmode())) {
|
||||
Boolean sendService = messengerChatbot.receiveMessage(c.getId(), fromId, toId, onlineUser, msgType, msg);
|
||||
if (sendService) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (c != null && !c.isEnabled() && StringUtils.equals(Constants.CHATBOT_CHATBOT_ONLY, c.getWorkmode())) {
|
||||
return;
|
||||
} else {
|
||||
agentUserRes.findOneByUseridAndStatusNotAndChannelAndOrgi(fromId, MainContext.AgentUserStatusEnum.END.toString(), MainContext.ChannelType.MESSENGER.toString(), Constants.SYSTEM_ORGI).ifPresent(p -> {
|
||||
if (p.isChatbotops()) {
|
||||
messengerChatbot.switchManualCustomerService(fromId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到OnlineUser后获取AgentUser
|
||||
* 因为AgentUser是和OnlineUser关联的
|
||||
*/
|
||||
// 一个OnlineUser可以对应多个agentUser, 此处获得
|
||||
AgentUser agentUser = agentUserRes.findOneByUseridAndStatusNotAndChannelAndOrgi(fromId, MainContext.AgentUserStatusEnum.END.toString(), MainContext.ChannelType.MESSENGER.toString(), Constants.SYSTEM_ORGI)
|
||||
.orElseGet(() -> new AgentUser());
|
||||
|
||||
AgentService agentService;
|
||||
|
||||
if (StringUtils.isBlank(agentUser.getAgentserviceid())) {
|
||||
FbMessenger fbMessenger = fbMessengerRepository.findOneByPageId(toId);
|
||||
if (fbMessenger != null && StringUtils.equals(fbMessenger.getStatus(), Constants.MESSENGER_CHANNEL_DISABLED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 没有加载到进行中的AgentUser,创建一个新的
|
||||
agentService = scheduleMessengerAgentUser(
|
||||
agentUser, onlineUser, snsAccount, Constants.SYSTEM_ORGI).orElseThrow(
|
||||
() -> new CSKefuException("Can not resolve AgentService Object."));
|
||||
} else {
|
||||
agentService = agentServiceRes.findOne(agentUser.getAgentserviceid());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 给客服发送消息
|
||||
*/
|
||||
if (agentUser != null && agentService != null) {
|
||||
ChatMessage chatMessage = new ChatMessage();
|
||||
Message outMessage = new Message();
|
||||
|
||||
chatMessage.setMessage(msg);
|
||||
chatMessage.setOrgi(Constants.SYSTEM_ORGI);
|
||||
chatMessage.setUsername(agentUser.getName());
|
||||
chatMessage.setCalltype(MainContext.CallType.IN.toString());
|
||||
if (StringUtils.isNotBlank(agentUser.getAgentno())) {
|
||||
chatMessage.setTouser(agentUser.getUserid());
|
||||
}
|
||||
chatMessage.setChannel(MainContext.ChannelType.MESSENGER.toString());
|
||||
chatMessage.setUsession(agentUser.getUserid());
|
||||
chatMessage.setId(MainUtils.getUUID());
|
||||
chatMessage.setContextid(agentUser.getContextid());
|
||||
chatMessage.setUserid(agentUser.getUserid());
|
||||
chatMessage.setUsession(agentUser.getUserid());
|
||||
chatMessage.setAgentserviceid(agentUser.getAgentserviceid());
|
||||
chatMessage.setUsername(agentUser.getUsername());
|
||||
|
||||
chatMessage.setMsgtype(msgType.toString());
|
||||
|
||||
outMessage.setMessageType(chatMessage.getMsgtype());
|
||||
outMessage.setMessage(msg);
|
||||
outMessage.setAttachmentid(chatMessage.getAttachmentid());
|
||||
outMessage.setCalltype(MainContext.CallType.IN.toString());
|
||||
outMessage.setContextid(agentUser.getContextid());
|
||||
outMessage.setAgentUser(agentUser);
|
||||
|
||||
outMessage.setChannelMessage(chatMessage);
|
||||
outMessage.setCreatetime(Constants.DISPLAY_DATE_FORMATTER.format(
|
||||
chatMessage.getCreatetime()));
|
||||
|
||||
outMessage.setMessage(chatMessage.getMessage());
|
||||
|
||||
outMessage.setChannelMessage(chatMessage);
|
||||
outMessage.setAgentUser(agentUser);
|
||||
outMessage.setAgentService(agentService);
|
||||
outMessage.setCalltype(
|
||||
MainContext.CallType.IN.toString());
|
||||
outMessage.setCreatetime(
|
||||
Constants.DISPLAY_DATE_FORMATTER.format(
|
||||
now));
|
||||
|
||||
|
||||
// Notify customer service to refresh the page
|
||||
if (StringUtils.isNotBlank(agentService.getAgentno())) {
|
||||
peerSyncIM.send(MainContext.ReceiverType.AGENT,
|
||||
MainContext.ChannelType.MESSENGER,
|
||||
agentUser.getAppid(),
|
||||
MainContext.MessageType.MESSAGE,
|
||||
agentService.getAgentno(), outMessage, true);
|
||||
} else {
|
||||
chatMessageRes.save((chatMessage));
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.info("[handle] agent user not found");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的AgentUser
|
||||
*
|
||||
* @param onlineUser 访客
|
||||
* @param snsAccount 社交信息账号
|
||||
* @param orgi 租户ID
|
||||
* @return
|
||||
*/
|
||||
public Optional<AgentService> scheduleMessengerAgentUser(
|
||||
final AgentUser agentUser,
|
||||
final OnlineUser onlineUser,
|
||||
final SNSAccount snsAccount,
|
||||
final String orgi) throws CSKefuException {
|
||||
if (agentUser == null) {
|
||||
throw new CSKefuException("Invalid param for agentUser, should not be null.");
|
||||
}
|
||||
|
||||
String channel = MainContext.ChannelType.MESSENGER.toString();
|
||||
Date now = new Date();
|
||||
agentUser.setUsername(onlineUser.getUsername());
|
||||
agentUser.setSkill(snsAccount.getOrgan());
|
||||
agentUser.setOrgi(orgi);
|
||||
agentUser.setNickname(onlineUser.getUsername());
|
||||
agentUser.setUserid(onlineUser.getUserid());
|
||||
agentUser.setStatus(MainContext.AgentUserStatusEnum.END.toString());
|
||||
agentUser.setLogindate(now);
|
||||
agentUser.setServicetime(now);
|
||||
agentUser.setCreatetime(now);
|
||||
agentUser.setUpdatetime(now);
|
||||
agentUser.setSessiontimes(System.currentTimeMillis() - now.getTime());
|
||||
agentUser.setChannel(channel);
|
||||
agentUser.setAppid(snsAccount.getSnsid());
|
||||
agentUserRes.save(agentUser);
|
||||
|
||||
// 为访客安排坐席
|
||||
ACDComposeContext ctx = acdMessageHelper.getComposeContextWithAgentUser(
|
||||
agentUser, false, MainContext.ChatInitiatorType.USER.toString());
|
||||
ctx.setOnlineUserHeadimgUrl(onlineUser.getHeadimgurl());
|
||||
|
||||
acdVisitorDispatcher.enqueue(ctx);
|
||||
acdAgentService.notifyAgentUserProcessResult(ctx);
|
||||
|
||||
|
||||
return Optional.ofNullable(ctx.getAgentService());
|
||||
}
|
||||
|
||||
public Map<String, String> getPersonName(String pageId, String psid) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
FbMessenger fbMessenger = fbMessengerRepository.findOneByPageId(pageId);
|
||||
if (fbMessenger != null) {
|
||||
Map<String, Object> searchParams = new HashMap<>();
|
||||
searchParams.put("access_token", fbMessenger.getToken());
|
||||
searchParams.put("fields", "locale,first_name,last_name,profile_pic");
|
||||
try {
|
||||
String res = HttpClientUtil.doGet("https://graph.facebook.com/" + psid, searchParams);
|
||||
JSONObject json = JSONObject.parseObject(res);
|
||||
String firstName = json.getString("first_name");
|
||||
String lastName = json.getString("last_name");
|
||||
|
||||
result.put("profile_pic", json.getString("profile_pic"));
|
||||
|
||||
saveProfilePic(result, json);
|
||||
|
||||
if (StringUtils.isNotBlank(firstName) && StringUtils.isNotBlank(lastName)) {
|
||||
result.put("name", firstName + " " + lastName);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("[messenger] 详情获取异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void saveProfilePic(Map<String, String> result, JSONObject json) {
|
||||
try {
|
||||
String profile_pic = json.getString("profile_pic");
|
||||
|
||||
if (StringUtils.isBlank(profile_pic)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CloseableHttpClient httpClient = HttpClients.createDefault();
|
||||
HttpGet httpGet = new HttpGet(profile_pic);
|
||||
|
||||
HttpResponse response = httpClient.execute(httpGet);
|
||||
//获取Http响应的码 200
|
||||
int startCode = response.getStatusLine().getStatusCode();
|
||||
HttpEntity entity = response.getEntity();
|
||||
if (startCode == 200 && entity != null) {
|
||||
InputStream input = entity.getContent();
|
||||
long size = entity.getContentLength();
|
||||
String fileid = MainUtils.getUUID();
|
||||
StreamingFile sf = new StreamingFile();
|
||||
sf.setId(fileid);
|
||||
sf.setName(fileid);
|
||||
sf.setMime(entity.getContentType().getValue());
|
||||
sf.setData(jpaBlobHelper.createBlob(input, size));
|
||||
streamingFileRes.save(sf);
|
||||
String fileURL = "/res/image.html?id=" + fileid;
|
||||
result.put("profile_pic", fileURL);
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void sendOTNContent(String fromId, String toId, JSONObject json) {
|
||||
if (json.getString("type").equals("image")) {
|
||||
sendImage(fromId, toId, json.getString("url"));
|
||||
} else if (json.getString("type").equals("text") && StringUtils.isNotBlank(json.getString("content"))) {
|
||||
send(fromId, toId, json.getString("content"));
|
||||
}
|
||||
}
|
||||
|
||||
public void sendOtnText(String fbToken, String otnToken, String msg) {
|
||||
logger.info("send messenger to otnToken:{} msg:{}", otnToken, msg);
|
||||
|
||||
JSONObject inputJson = JSONObject.parseObject(msg);
|
||||
|
||||
try {
|
||||
JSONObject json = new JSONObject();
|
||||
JSONObject recipient = new JSONObject();
|
||||
recipient.put("one_time_notif_token", otnToken);
|
||||
JSONObject message = new JSONObject();
|
||||
if (inputJson.getString("type").equals("image")) {
|
||||
JSONObject attachment = new JSONObject();
|
||||
attachment.put("type", "image");
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("url", "https://" + host + inputJson.getString("url"));
|
||||
attachment.put("payload", payload);
|
||||
message.put("attachment", attachment);
|
||||
json.put("recipient", recipient);
|
||||
json.put("message", message);
|
||||
} else {
|
||||
message.put("text", inputJson.getString("content"));
|
||||
message.put("metadata", "DEVELOPER_DEFINED_METADATA");
|
||||
json.put("recipient", recipient);
|
||||
json.put("message", message);
|
||||
}
|
||||
|
||||
String result = HttpClientUtil.doPost(FACEBOOK_MESSAGES_API + "?access_token=" + fbToken, json.toJSONString());
|
||||
logger.info(result);
|
||||
} catch (IOException e) {
|
||||
logger.error("[messenger] 发送消息异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void send(String fromId, String toId, JSONObject message) {
|
||||
send(fromId, toId, message, null);
|
||||
}
|
||||
|
||||
public void send(String fromId, String toId, JSONObject message, FbMessenger fbMessenger) {
|
||||
if (fbMessenger == null) {
|
||||
fbMessenger = fbMessengerRepository.findOneByPageId(fromId);
|
||||
}
|
||||
|
||||
if (fbMessenger != null) {
|
||||
try {
|
||||
JSONObject json = new JSONObject();
|
||||
JSONObject recipient = new JSONObject();
|
||||
recipient.put("id", toId);
|
||||
json.put("recipient", recipient);
|
||||
json.put("message", message);
|
||||
|
||||
String result = HttpClientUtil.doPost(FACEBOOK_MESSAGES_API + "?access_token=" + fbMessenger.getToken(), json.toJSONString());
|
||||
logger.info(result);
|
||||
} catch (IOException e) {
|
||||
logger.error("[messenger] 发送消息异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sendImage(String fromId, String toId, String imageUrl) {
|
||||
logger.info("send messenger fromId:{} toId:{} image:{}", fromId, toId, imageUrl);
|
||||
|
||||
JSONObject message = new JSONObject();
|
||||
JSONObject attachment = new JSONObject();
|
||||
attachment.put("type", "image");
|
||||
JSONObject payload = new JSONObject();
|
||||
if (StringUtils.indexOf(imageUrl, "http") > -1) {
|
||||
payload.put("url", imageUrl);
|
||||
} else {
|
||||
payload.put("url", "https://" + host + imageUrl);
|
||||
}
|
||||
attachment.put("payload", payload);
|
||||
message.put("attachment", attachment);
|
||||
|
||||
send(fromId, toId, message);
|
||||
}
|
||||
|
||||
public void send(String fromId, String toId, String msg) {
|
||||
logger.info("send messenger fromId:{} toId:{} msg:{}", fromId, toId, msg);
|
||||
|
||||
JSONObject message = new JSONObject();
|
||||
message.put("text", StringEscapeUtils.unescapeHtml(msg));
|
||||
|
||||
send(fromId, toId, message);
|
||||
}
|
||||
|
||||
public void sendOTNReq(String fromId, String toId, String title, String ref) {
|
||||
logger.info("send messenger fromId:{} toId:{} title:{}", fromId, toId, title);
|
||||
|
||||
FbMessenger fbMessenger = fbMessengerRepository.findOneByPageId(fromId);
|
||||
if (fbMessenger != null) {
|
||||
try {
|
||||
JSONObject json = new JSONObject();
|
||||
JSONObject recipient = new JSONObject();
|
||||
recipient.put("id", toId);
|
||||
JSONObject message = new JSONObject();
|
||||
JSONObject attachment = new JSONObject();
|
||||
attachment.put("type", "template");
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("template_type", "one_time_notif_req");
|
||||
payload.put("title", title);
|
||||
payload.put("payload", ref);
|
||||
attachment.put("payload", payload);
|
||||
message.put("attachment", attachment);
|
||||
json.put("recipient", recipient);
|
||||
json.put("message", message);
|
||||
|
||||
String result = HttpClientUtil.doPost(FACEBOOK_MESSAGES_API + "?access_token=" + fbMessenger.getToken(), json.toJSONString());
|
||||
logger.info(result);
|
||||
} catch (IOException e) {
|
||||
logger.error("[messenger] 发送消息异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
package com.chatopera.cc.plugins.messenger;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.chatopera.cc.activemq.BrokerPublisher;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.controller.Handler;
|
||||
import com.chatopera.cc.controller.api.request.RestUtils;
|
||||
import com.chatopera.cc.exception.CSKefuException;
|
||||
import com.chatopera.cc.model.*;
|
||||
import com.chatopera.cc.persistence.repository.FbMessengerRepository;
|
||||
import com.chatopera.cc.persistence.repository.FbOTNRepository;
|
||||
import com.chatopera.cc.proxy.AgentProxy;
|
||||
import com.chatopera.cc.proxy.OrganProxy;
|
||||
import com.chatopera.cc.util.Menu;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.validation.Valid;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/apps/messenger/otn")
|
||||
public class MessengerOTNController extends Handler {
|
||||
@Autowired
|
||||
private FbMessengerRepository fbMessengerRepository;
|
||||
|
||||
@Autowired
|
||||
private FbOTNRepository otnRepository;
|
||||
|
||||
@Autowired
|
||||
private OrganProxy organProxy;
|
||||
|
||||
@Autowired
|
||||
private AgentProxy agentProxy;
|
||||
|
||||
@Autowired
|
||||
private BrokerPublisher brokerPublisher;
|
||||
|
||||
private Map<String, Organ> getOwnOrgan(HttpServletRequest request) {
|
||||
return organProxy.findAllOrganByParentAndOrgi(super.getOrgan(request), super.getOrgi(request));
|
||||
}
|
||||
|
||||
@RequestMapping("/index")
|
||||
@Menu(type = "admin", subtype = "messenger")
|
||||
public ModelAndView index(ModelMap map, HttpServletRequest request, @Valid String queryPageId) {
|
||||
Map<String, Organ> organs = getOwnOrgan(request);
|
||||
List<FbMessenger> fbMessengers = fbMessengerRepository.findByOrganIn(organs.keySet());
|
||||
List<String> pageIds = fbMessengers.stream().map(p -> p.getPageId()).collect(Collectors.toList());
|
||||
|
||||
map.addAttribute("fbMessengers", fbMessengers);
|
||||
|
||||
if (StringUtils.isNotBlank(queryPageId)) {
|
||||
map.addAttribute("queryPageId", queryPageId);
|
||||
pageIds = Arrays.asList(queryPageId);
|
||||
}
|
||||
|
||||
Page<FbOTN> otns = otnRepository.findByPageIdIn(pageIds, new PageRequest(super.getP(request), super.getPs(request), Sort.Direction.DESC, "createtime"));
|
||||
map.addAttribute("otns", otns);
|
||||
return request(super.createView("/admin/channel/messenger/otn/index"));
|
||||
}
|
||||
|
||||
@RequestMapping("/add")
|
||||
@Menu(type = "admin", subtype = "messenger")
|
||||
public ModelAndView add(ModelMap map, HttpServletRequest request) {
|
||||
Map<String, Organ> organs = getOwnOrgan(request);
|
||||
List<FbMessenger> fbMessengers = fbMessengerRepository.findByOrganIn(organs.keySet());
|
||||
map.addAttribute("fbMessengers", fbMessengers);
|
||||
return request(super.createView("/admin/channel/messenger/otn/add"));
|
||||
}
|
||||
|
||||
@RequestMapping("/save")
|
||||
@Menu(type = "admin", subtype = "messenger")
|
||||
public ModelAndView save(ModelMap map, @Valid FbOTN otn, HttpServletRequest request) {
|
||||
String msg = "save_ok";
|
||||
otn.setId(MainUtils.getUUID());
|
||||
otn.setCreatetime(new Date());
|
||||
otn.setUpdatetime(new Date());
|
||||
otn.setSubNum(0);
|
||||
otn.setMelinkNum(0);
|
||||
|
||||
otn.setStatus("create");
|
||||
otnRepository.save(otn);
|
||||
|
||||
if (otn.getSendtime() != null) {
|
||||
delaySend(otn);
|
||||
}
|
||||
|
||||
return request(super.createView("redirect:/apps/messenger/otn/index.html?msg=" + msg));
|
||||
}
|
||||
|
||||
@RequestMapping("/edit")
|
||||
@Menu(type = "admin", subtype = "messenger")
|
||||
public ModelAndView edit(ModelMap map, @Valid String id, HttpServletRequest request) {
|
||||
map.addAttribute("otn", otnRepository.getOne(id));
|
||||
return request(super.createView("/admin/channel/messenger/otn/edit"));
|
||||
}
|
||||
|
||||
@RequestMapping("/update")
|
||||
@Menu(type = "admin", subtype = "messenger")
|
||||
public ModelAndView update(ModelMap map, @Valid FbOTN otn, HttpServletRequest request) {
|
||||
String msg = "update_ok";
|
||||
FbOTN oldOtn = otnRepository.findOne(otn.getId());
|
||||
if (oldOtn != null) {
|
||||
Date oldSendtime = oldOtn.getSendtime();
|
||||
|
||||
oldOtn.setName(otn.getName());
|
||||
oldOtn.setPreSubMessage(otn.getPreSubMessage());
|
||||
oldOtn.setSubMessage(otn.getSubMessage());
|
||||
oldOtn.setOtnMessage(otn.getOtnMessage());
|
||||
oldOtn.setSuccessMessage(otn.getSuccessMessage());
|
||||
oldOtn.setSendtime(otn.getSendtime());
|
||||
oldOtn.setUpdatetime(new Date());
|
||||
otnRepository.save((oldOtn));
|
||||
|
||||
if (otn.getSendtime() != null && !otn.getSendtime().equals(oldSendtime)) {
|
||||
delaySend(otn);
|
||||
}
|
||||
}
|
||||
|
||||
return request(super.createView("redirect:/apps/messenger/otn/index.html?msg=" + msg));
|
||||
}
|
||||
|
||||
private void delaySend(FbOTN otn) {
|
||||
long delaySeconds = (otn.getSendtime().getTime() - new Date().getTime()) / 1000;
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("otnId", otn.getId());
|
||||
payload.put("sendtime", otn.getSendtime());
|
||||
brokerPublisher.send(Constants.INSTANT_MESSAGING_MQ_QUEUE_FACEBOOK_OTN, payload.toJSONString(), false, (int) delaySeconds);
|
||||
}
|
||||
|
||||
@RequestMapping("/send")
|
||||
@Menu(type = "admin", subtype = "faceb ook")
|
||||
public ModelAndView send(ModelMap map, @Valid String id, HttpServletRequest request) {
|
||||
String msg = "send_ok";
|
||||
|
||||
FbOTN otn = otnRepository.getOne(id);
|
||||
FbMessenger fbMessenger = fbMessengerRepository.findOneByPageId(otn.getPageId());
|
||||
if (fbMessenger != null && otn != null && otn.getStatus().equals("create")) {
|
||||
otn.setStatus("sending");
|
||||
otn.setSendtime(new Date());
|
||||
otnRepository.save(otn);
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("otnId", otn.getId());
|
||||
brokerPublisher.send(Constants.INSTANT_MESSAGING_MQ_QUEUE_FACEBOOK_OTN, payload.toJSONString());
|
||||
}
|
||||
|
||||
return request(super.createView("redirect:/apps/messenger/otn/index.html?msg=" + msg));
|
||||
}
|
||||
|
||||
@RequestMapping("/image/upload")
|
||||
@Menu(type = "admin", subtype = "image", access = false)
|
||||
public ResponseEntity<String> upload(
|
||||
ModelMap map,
|
||||
HttpServletRequest request,
|
||||
@RequestParam(value = "imgFile", required = false) MultipartFile multipart
|
||||
) throws IOException {
|
||||
final User logined = super.getUser(request);
|
||||
JSONObject result = new JSONObject();
|
||||
HttpHeaders headers = RestUtils.header();
|
||||
if (multipart != null && multipart.getOriginalFilename().lastIndexOf(".") > 0) {
|
||||
try {
|
||||
StreamingFile sf = agentProxy.saveFileIntoMySQLBlob(logined, multipart);
|
||||
result.put("error", 0);
|
||||
result.put("url", sf.getFileUrl());
|
||||
} catch (CSKefuException e) {
|
||||
result.put("error", 1);
|
||||
result.put("message", "请选择文件");
|
||||
}
|
||||
} else {
|
||||
result.put("error", 1);
|
||||
result.put("message", "请选择图片文件");
|
||||
}
|
||||
return new ResponseEntity<>(result.toString(), headers, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@RequestMapping("/delete")
|
||||
@Menu(type = "admin", subtype = "messenger")
|
||||
public ModelAndView delete(ModelMap map, HttpServletRequest request, @Valid String id) {
|
||||
String msg = "delete_ok";
|
||||
otnRepository.delete(id);
|
||||
|
||||
return request(super.createView("redirect:/apps/messenger/otn/index.html?msg=" + msg));
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* 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 com.chatopera.cc.plugins.messenger;
|
||||
|
||||
import com.chatopera.cc.basic.plugins.AbstractPluginConfigurer;
|
||||
import com.chatopera.cc.basic.plugins.PluginRegistry;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
|
||||
/**
|
||||
* 定义Plugin存在
|
||||
*/
|
||||
@Configuration
|
||||
public class MessengerPluginConfigurer extends AbstractPluginConfigurer {
|
||||
private final static Logger logger = LoggerFactory.getLogger(MessengerPluginConfigurer.class);
|
||||
private final static String pluginName = "Messenger 渠道";
|
||||
private final static String pluginId = "messenger";
|
||||
|
||||
@Autowired
|
||||
private PluginRegistry pluginRegistry;
|
||||
|
||||
@PostConstruct
|
||||
public void setup() {
|
||||
pluginRegistry.addPlugin(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPluginId() {
|
||||
return pluginId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息服务的Bean的名字
|
||||
* 当该方法存在时,加载到消息处理的调用栈 PeerSyncIM 中
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String getPluginName() {
|
||||
return pluginName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIOEventHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModule() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* 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 com.chatopera.cc.plugins.messenger;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.JSONPath;
|
||||
import com.chatopera.bot.exception.ChatbotException;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.controller.Handler;
|
||||
import com.chatopera.cc.exception.CSKefuException;
|
||||
import com.chatopera.cc.model.FbMessenger;
|
||||
import com.chatopera.cc.persistence.repository.FbMessengerRepository;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/messenger")
|
||||
public class MessengerWebhookChannelController extends Handler {
|
||||
private final static Logger logger = LoggerFactory.getLogger(MessengerWebhookChannelController.class);
|
||||
|
||||
@Autowired
|
||||
private FbMessengerRepository fbMessengerRepository;
|
||||
|
||||
@Autowired
|
||||
private MessengerMessageProxy messengerMessageProxy;
|
||||
|
||||
@Autowired
|
||||
private MessengerChatbot messengerChatbot;
|
||||
|
||||
@RequestMapping(value = "/webhook/{pageId}", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public String get(@PathVariable("pageId") String pageId, @RequestParam("hub.challenge") String challenge, @RequestParam("hub.verify_token") String verify_token) {
|
||||
logger.info("[get] verify token pageid {}", pageId);
|
||||
FbMessenger fbMessenger = fbMessengerRepository.findOneByPageId(pageId);
|
||||
String result = "not allow!";
|
||||
|
||||
if (fbMessenger != null && fbMessenger.getVerifyToken().equals(verify_token)) {
|
||||
result = challenge;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/webhook/{pageId}", method = RequestMethod.POST)
|
||||
@ResponseBody
|
||||
public String post(@PathVariable("pageId") String pageId, @RequestBody JSONObject jsonParam) {
|
||||
logger.info("[post] pageId {}, payload {}", pageId, jsonParam.toString());
|
||||
// NOTE: 此处 PathVariable pageId 不安全,不建议在函数内使用,详见 #1190
|
||||
// https://gitlab.chatopera.com/cskefu/cskefu.io/issues/1190
|
||||
// 该值可使用 recipientId 更为准确,recipientId 就是实际的 pageId
|
||||
if (jsonParam.getString("object").equals("page")) {
|
||||
JSONArray entries = jsonParam.getJSONArray("entry");
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
JSONObject entry = entries.getJSONObject(i);
|
||||
JSONArray messaging = entry.getJSONArray("messaging");
|
||||
for (int j = 0; j < messaging.size(); j++) {
|
||||
try {
|
||||
messageHandler(messaging.getJSONObject(j));
|
||||
} catch (Exception e) {
|
||||
logger.error("[messenger] 接收消息异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private void messageHandler(JSONObject messagingEvent) throws ChatbotException, CSKefuException, MalformedURLException {
|
||||
String senderId = (String) JSONPath.eval(messagingEvent, "$.sender.id");
|
||||
String recipientId = (String) JSONPath.eval(messagingEvent, "$.recipient.id");
|
||||
String referralType = (String) JSONPath.eval(messagingEvent, "$.referral.type");
|
||||
String optinType = (String) JSONPath.eval(messagingEvent, "$.optin.type");
|
||||
String postbackPayload = (String) JSONPath.eval(messagingEvent, "$.postback.payload");
|
||||
JSONObject quickRepliesPayload = (JSONObject) JSONPath.eval(messagingEvent, "$.message.quick_reply");
|
||||
|
||||
if (StringUtils.equals(referralType, "OPEN_THREAD")) {
|
||||
String ref = (String) JSONPath.eval(messagingEvent, "$.referral.ref");
|
||||
messengerMessageProxy.acceptMeLink(senderId, recipientId, ref);
|
||||
return;
|
||||
} else if (StringUtils.equals(optinType, "one_time_notif_req")) {
|
||||
String otnToken = (String) JSONPath.eval(messagingEvent, "$.optin.one_time_notif_token");
|
||||
String ref = (String) JSONPath.eval(messagingEvent, "$.optin.payload");
|
||||
messengerMessageProxy.acceptOTNReq(senderId, recipientId, otnToken, ref);
|
||||
return;
|
||||
} else if (StringUtils.isNotBlank(postbackPayload)) {
|
||||
if (StringUtils.equals(postbackPayload, "TRANSFER_LABOR")) {
|
||||
messengerChatbot.switchManualCustomerService(senderId);
|
||||
} else if (StringUtils.equals(postbackPayload, "FAQ_LIST")) {
|
||||
String msg = (String) JSONPath.eval(messagingEvent, "$.postback.title");
|
||||
messengerMessageProxy.accept(senderId, recipientId, MainContext.MediaType.TEXT, msg);
|
||||
} else if (StringUtils.equals(postbackPayload, "startChatopera")) {
|
||||
messengerMessageProxy.accept(senderId, recipientId, MainContext.MediaType.TEXT, "__kickoff");
|
||||
} else {
|
||||
messengerMessageProxy.accept(senderId, recipientId, MainContext.MediaType.TEXT, postbackPayload);
|
||||
}
|
||||
return;
|
||||
} else if (quickRepliesPayload != null) {
|
||||
String msg = (String) JSONPath.eval(messagingEvent, "$.message.quick_reply.payload");
|
||||
messengerMessageProxy.accept(senderId, recipientId, MainContext.MediaType.TEXT, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
String msg = (String) JSONPath.eval(messagingEvent, "$.message.text");
|
||||
JSONArray attachments = (JSONArray) JSONPath.eval(messagingEvent, "$.message.attachments");
|
||||
|
||||
if (StringUtils.isNotBlank(msg)) {
|
||||
messengerMessageProxy.accept(senderId, recipientId, MainContext.MediaType.TEXT, msg);
|
||||
} else {
|
||||
for (Object att : attachments) {
|
||||
|
||||
String type = (String) JSONPath.eval(att, "$.type");
|
||||
String url = (String) JSONPath.eval(att, "$.payload.url");
|
||||
|
||||
if (StringUtils.equals(type, "image")) {
|
||||
messengerMessageProxy.accept(senderId, recipientId, MainContext.MediaType.IMAGE, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
.uk-layui-form(style="margin:20px auto")
|
||||
form.layui-form(action="/admin/messenger/save.html", method="post")
|
||||
.layui-form-item
|
||||
.layui-inline
|
||||
label.layui-form-label(style="width:150px;")
|
||||
| 技能组
|
||||
.layui-input-inline
|
||||
input.layui-input(style="width: 200px;", type="text", name="name", required="", lay-verify="required", autocomplete="off", value="#{organ.name}", disabled="disabled")
|
||||
i.csfont(style='position: absolute;right: 3px;top: 8px;font-size: 20px;color: #e6e6e6') 
|
||||
.layui-form-mid.layui-word-aux
|
||||
| 该渠道的人工服务由哪个技能组接待,默认为当前技能组,在右上角切换
|
||||
.layui-form-item
|
||||
.layui-inline
|
||||
label.layui-form-label(style="width:150px;")
|
||||
| 名称
|
||||
font(color='red') *
|
||||
.layui-input-inline
|
||||
input.layui-input(style="width: 200px;", type="text", name="name", required="", lay-verify="required", autocomplete="off", placeholder="北美玩家页")
|
||||
.layui-form-mid.layui-word-aux
|
||||
| 使用字符串命名该渠道
|
||||
.layui-form-item
|
||||
.layui-inline
|
||||
label.layui-form-label(style="width:150px;")
|
||||
| Page ID
|
||||
font(color='red') *
|
||||
.layui-input-inline
|
||||
input.layui-input(style="width: 200px;", type="text", name="pageId", required="", lay-verify="required", autocomplete="off", placeholder="e.g. 1541840459")
|
||||
.layui-form-mid.layui-word-aux
|
||||
| Facebook Messenger 设置页面获得 Page Id
|
||||
.layui-form-item
|
||||
.layui-inline
|
||||
label.layui-form-label(style="width:150px;")
|
||||
| Access Token
|
||||
font(color='red') *
|
||||
.layui-input-inline
|
||||
input.layui-input(style="width: 200px;", type="password", name="token", required="", lay-verify="required", autocomplete="off", placeholder="*********")
|
||||
.layui-form-mid.layui-word-aux
|
||||
| Facebook Messenger 设置页面获得 Access Token
|
||||
.layui-form-item
|
||||
.layui-inline
|
||||
label.layui-form-label(style="width:150px;")
|
||||
| Verify Token
|
||||
font(color='red') *
|
||||
.layui-input-inline
|
||||
input.layui-input(style="width: 200px;", type="text", name="verifyToken", required="", lay-verify="required", autocomplete="off", placeholder="e.g. uN26itM4Ec")
|
||||
.layui-form-mid.layui-word-aux
|
||||
| 自定义,并且回填到 Facebook Messenger 设置页面
|
||||
.layui-form-button
|
||||
.layui-button-block
|
||||
button.layui-btn(lay-submit="", lay-filter="formDemo") 提交
|
||||
button.layui-btn.layui-btn-original(type="reset") 重置
|
||||
script.
|
||||
layui.use('form', function () {
|
||||
var form = layui.form();
|
||||
form.render(); //更新全部
|
||||
});
|
||||
layui.use('element', function () {
|
||||
var element = layui.element();
|
||||
});
|
@ -0,0 +1,61 @@
|
||||
.uk-layui-form(style="margin:20px auto")
|
||||
form.layui-form(action="/admin/messenger/update.html", method="post")
|
||||
input(type="hidden", name="id", value=fb.id)
|
||||
input(type="hidden", name="status", value=fb.status)
|
||||
.layui-form-item
|
||||
.layui-inline
|
||||
label.layui-form-label(style="width:150px;")
|
||||
| 技能组
|
||||
.layui-input-inline
|
||||
input.layui-input(style="width: 200px;", type="text", name="name", required="", lay-verify="required", autocomplete="off", value=organ.name, disabled="disabled")
|
||||
i.csfont(style='position: absolute;right: 3px;top: 8px;font-size: 20px;color: #e6e6e6') 
|
||||
.layui-form-mid.layui-word-aux
|
||||
| 该渠道的人工服务由哪个技能组接待,默认为当前技能组,在右上角切换
|
||||
.layui-form-item
|
||||
.layui-inline
|
||||
label.layui-form-label(style="width:150px;")
|
||||
| 名称
|
||||
font(color='red') *
|
||||
.layui-input-inline
|
||||
input.layui-input(style="width: 200px;", type="text", name="name", required="", lay-verify="required", autocomplete="off", value=fb.name)
|
||||
.layui-form-mid.layui-word-aux
|
||||
| 使用字符串命名该渠道
|
||||
.layui-form-item
|
||||
.layui-inline
|
||||
label.layui-form-label(style="width:150px;")
|
||||
| Page ID
|
||||
font(color='red') *
|
||||
.layui-input-inline
|
||||
input.layui-input(style="width: 200px;", disabled ,type="text", name="pageId", required="", lay-verify="required", autocomplete="off", value=fb.pageId)
|
||||
.layui-form-mid.layui-word-aux
|
||||
| Facebook Messenger 设置页面获得 Page Id
|
||||
.layui-form-item
|
||||
.layui-inline
|
||||
label.layui-form-label(style="width:150px;")
|
||||
| Access Token
|
||||
font(color='red') *
|
||||
.layui-input-inline
|
||||
input.layui-input(style="width: 200px;", type="password", name="token", required="", lay-verify="required", autocomplete="off", value=fb.token)
|
||||
.layui-form-mid.layui-word-aux
|
||||
| Facebook Messenger 设置页面获得 Access Token
|
||||
.layui-form-item
|
||||
.layui-inline
|
||||
label.layui-form-label(style="width:150px;")
|
||||
| Verify Token
|
||||
font(color='red') *
|
||||
.layui-input-inline
|
||||
input.layui-input(style="width: 200px;", type="text", name="verifyToken", required="", lay-verify="required", autocomplete="off", value=fb.verifyToken)
|
||||
.layui-form-mid.layui-word-aux
|
||||
| 自定义,并且回填到 Facebook Messenger 设置页面
|
||||
.layui-form-button
|
||||
.layui-button-block
|
||||
button.layui-btn(lay-submit="", lay-filter="formDemo") 提交
|
||||
button.layui-btn.layui-btn-original(type="reset") 重置
|
||||
script.
|
||||
layui.use('form', function () {
|
||||
var form = layui.form();
|
||||
form.render(); //更新全部
|
||||
});
|
||||
layui.use('element', function () {
|
||||
var element = layui.element();
|
||||
});
|
@ -0,0 +1,17 @@
|
||||
.row(style="border-bottom: 10px solid #EFEFEF;padding:10px;")
|
||||
.col-lg-8
|
||||
.ukefu-measure
|
||||
.ukefu-bt
|
||||
.layui-icon.ukewo-btn.ukefu-content-ind
|
||||
img(src="/images/messenger.png", style="width:60px;height:60px;")
|
||||
.ukefu-bt-text
|
||||
.ukefu-bt-text-title(style="font-weight:400;font-size:24px;border-bottom:1px solid #dedede;") #{fb.name}
|
||||
span(style="font-size:15px;color:#AAAAAA;") 接入渠道创建时间:#{pugHelper.formatDate('yyyy-MM-dd HH:mm:ss', fb.createtime)}
|
||||
|
||||
.ukefu-bt-text-content(style="") Facebook Page Id: #{fb.pageId}
|
||||
.ukefu-tab-title(style="margin-left: 0px;")
|
||||
ul.tab-title
|
||||
.ukefu-tab-title(style="margin-left: 0px;")
|
||||
ul.tab-title
|
||||
li(class={'layui-this': subtype == 'messenger'})
|
||||
a(href="/admin/messenger/setting.html?id=" + fb.id) 基本设置
|
@ -0,0 +1,176 @@
|
||||
extends /admin/include/layout.pug
|
||||
|
||||
block content
|
||||
.row
|
||||
input#copyInput(type="hidden")
|
||||
#me-config.col-lg-12
|
||||
h1.site-h1(style="background-color:#FFFFFF;")
|
||||
| Messenger 列表 (#{size(fbMessengers)})
|
||||
span(style="float:right;")
|
||||
.layui-btn-group
|
||||
if organ.skill == true
|
||||
button.layui-btn.layui-btn-small.green(href="/admin/messenger/add.html", data-toggle="ajax", data-width="800", data-height="400", data-title="创建 Messenger 渠道") 创建渠道
|
||||
else
|
||||
button.layui-btn.layui-btn-disabled.layui-btn-small.layui-btn-warm(data-toggle="tooltip" title="当前组织机构非技能组,不支持创建 Messenger 渠道,使用右上角菜单切换") 创建渠道
|
||||
button.layui-btn.layui-btn-small.layui-btn-normal(onclick='openExternalUrlWithBlankTarget("https://docs.chatopera.com/products/cskefu/channels/messenger/index.html")') 文档中心
|
||||
.row(style="padding:5px;")
|
||||
blockquote.layui-elem-quote.layui-quote-nm
|
||||
i.layui-icon(style="color:gray") 
|
||||
font(color="#999").layui-word-aux Messenger 是 Facebook 提供给企业、商铺、消费者之间相互连接的即时通信工具,与 Facebook 粉丝页、Instagram 和网页聊天等多渠道快速发起对话,春松客服支持与 Messenger 集成,如有疑问,阅读文档中心,获得详细使用指南。
|
||||
.col-lg-12
|
||||
table.layui-table(lay-skin="line", style="table-layout: fixed; word-break: break-all")
|
||||
thead
|
||||
tr
|
||||
th(style="width:100px") 名称
|
||||
th 技能组
|
||||
th Page ID
|
||||
th(style="width:150px") 创建时间
|
||||
th
|
||||
| 状态
|
||||
i.layui-icon(style="color:gray" data-toggle="tooltip" title="处于关闭状态,只对该渠道支持 Facebook OTN 功能;在开启状态,即支持客服功能也支持 Facebook OTN 功能") 
|
||||
th Webhook
|
||||
th(style="width:250px;white-space:nowrap;", nowrap="nowrap") 操作
|
||||
tbody
|
||||
for item in fbMessengers
|
||||
tr
|
||||
td(title="#{item.name}", style="text-overflow: ellipsis;white-space: nowrap;overflow: hidden;")= item.name
|
||||
td(title="", style="text-overflow: ellipsis;white-space: nowrap;overflow: hidden;")= organs[item.organ].name
|
||||
td(title="#{item.pageId}", style="text-overflow: ellipsis;white-space: nowrap;overflow: hidden;")= item.pageId
|
||||
td(title="", style="text-overflow: ellipsis;white-space: nowrap;overflow: hidden;")= pugHelper.formatDate("yyyy-MM-dd HH:mm:ss", item.createtime)
|
||||
td(style="text-overflow: ellipsis;white-space: nowrap;overflow: hidden;")
|
||||
.fbStatus.layui-unselect.layui-form-switch.checkStatus.lay-filter(style="margin-top: 0px;width: 50px;", data-id=item.id, class={
|
||||
'layui-form-onswitch': item.status != 'disabled',
|
||||
'layui-form-offswitch': item.status == 'disabled'
|
||||
})
|
||||
i.checkStatusI
|
||||
td(style="text-overflow: ellipsis;white-space: nowrap;overflow: hidden;")
|
||||
button.layui-btn.layui-btn-small.webhook(type="button", data-pageid=item.pageId) 复制
|
||||
td(title="", style="text-overflow: ellipsis;white-space: nowrap;overflow: hidden;")
|
||||
a(href="/admin/messenger/setting.html?id=" + item.id, data-toggle="load", data-target="#me-config")
|
||||
i.layui-icon
|
||||
| 设置
|
||||
a(href="/admin/messenger/edit.html?id=" + item.id, style="margin-left:10px;", data-toggle="ajax", data-width="800", data-height="400", data-title="编辑 Messenger 渠道")
|
||||
i.layui-icon
|
||||
| 编辑
|
||||
a(href="https://www.facebook.com/" + item.pageId, style="margin-left:10px;", target="_blank")
|
||||
i.layui-icon 
|
||||
| 粉丝页
|
||||
a(href="/admin/messenger/delete.html?id=" + item.id, style="margin-left:10px;", data-toggle="tip", title="请确认是否删除?")
|
||||
i.layui-icon(style="color:red;") ဆ
|
||||
| 删除
|
||||
|
||||
style.
|
||||
.fbStatus.layui-form-onswitch .checkStatusI {
|
||||
left: 38px;
|
||||
}
|
||||
|
||||
.fbStatus.layui-form-onswitch:before {
|
||||
content: '开启';
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.fbStatus.layui-form-offswitch:before {
|
||||
content: '关闭';
|
||||
color: #aaaaaa;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
script.
|
||||
// 打开链接
|
||||
function openExternalUrlWithBlankTarget(externalUrl) {
|
||||
window.open(externalUrl, "_blank");
|
||||
}
|
||||
|
||||
layui.use('layer', function () {
|
||||
var layer = layui.layer;
|
||||
var msg = '#{msg}'
|
||||
if (msg == 'save_ok')
|
||||
layer.msg('渠道添加成功', {icon: 1, time: 1000})
|
||||
else if (msg == 'update_ok')
|
||||
layer.msg('保存成功', {icon: 1, time: 1000})
|
||||
else if (msg == 'save_no_PageId')
|
||||
layer.msg('Page ID已存在', {icon: 2, time: 3000})
|
||||
});
|
||||
|
||||
$('.fbStatus').click(function () {
|
||||
var isOpen = $(this).hasClass('layui-form-onswitch')
|
||||
var id = $(this).data('id');
|
||||
var status = !isOpen ? 'enabled' : 'disabled';
|
||||
|
||||
var that = this;
|
||||
|
||||
$.post('setStatus.html', {id, status}).success(function (msg) {
|
||||
if (msg == 'ok') {
|
||||
if (isOpen) {
|
||||
$(that).removeClass('layui-form-onswitch');
|
||||
$(that).addClass('layui-form-offswitch');
|
||||
} else {
|
||||
$(that).removeClass('layui-form-offswitch');
|
||||
$(that).addClass('layui-form-onswitch');
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
$('.webhook').each(function (i, n) {
|
||||
var htmlOld = $(n).attr("data-pageId");
|
||||
var webhook = window.location.protocol + "//" + window.location.host + "/messenger/webhook/" + htmlOld;
|
||||
$(n).on('click', function () {
|
||||
Clipboard.copy(webhook);
|
||||
})
|
||||
});
|
||||
window.Clipboard = (function (window, document, navigator) {
|
||||
var textArea,
|
||||
copy;
|
||||
|
||||
// 判断是不是ios端
|
||||
function isOS() {
|
||||
return navigator.userAgent.match(/ipad|iphone/i);
|
||||
}
|
||||
|
||||
//创建文本元素
|
||||
function createTextArea(text) {
|
||||
textArea = document.createElement('textArea');
|
||||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
}
|
||||
|
||||
//选择内容
|
||||
function selectText() {
|
||||
var range,
|
||||
selection;
|
||||
if (isOS()) {
|
||||
range = document.createRange();
|
||||
range.selectNodeContents(textArea);
|
||||
selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
textArea.setSelectionRange(0, 999999);
|
||||
} else {
|
||||
textArea.select();
|
||||
}
|
||||
}
|
||||
|
||||
//复制到剪贴板
|
||||
function copyToClipboard() {
|
||||
try {
|
||||
if (document.execCommand("Copy")) {
|
||||
layer.msg('webhook已复制到剪切板', {icon: 1, time: 1000, offset: 'rt'})
|
||||
} else {
|
||||
layer.msg('webhook复制失败请手动复制', {icon: 2, time: 1000, offset: 'rt'})
|
||||
}
|
||||
} catch (err) {
|
||||
layer.msg('webhook复制失败请手动复制', {icon: 2, time: 1000, offset: 'rt'})
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
|
||||
copy = function (text) {
|
||||
createTextArea(text);
|
||||
selectText();
|
||||
copyToClipboard();
|
||||
};
|
||||
return {
|
||||
copy: copy
|
||||
};
|
||||
})(window, document, navigator);
|
@ -0,0 +1,133 @@
|
||||
.row
|
||||
.col-lg-12
|
||||
.ukefu-customer-div.setting-wrapper
|
||||
.box.default-box
|
||||
.box-header
|
||||
h1.site-h1 新建OTN
|
||||
.row
|
||||
.col-lg-12
|
||||
.uk-layui-form(style="height: calc(100% + 10px);")
|
||||
form.layui-form(action="/apps/messenger/otn/save.html", method="post")
|
||||
input#preSubMessage(type="hidden", name="preSubMessage", value=otn.preSubMessage)
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 基本信息
|
||||
.layui-colla-content.layui-show
|
||||
.layui-form-item
|
||||
.layui-inline
|
||||
label.layui-form-label 名称:
|
||||
.layui-input-inline
|
||||
input.layui-input(type='text', name='name', required, lay-verify='required', autocomplete='off')
|
||||
.layui-inline
|
||||
font(color='red') *(必填项)
|
||||
.layui-inline
|
||||
label.layui-form-label(style='width:60px;line-height: 35px;') 渠道:
|
||||
.layui-input-inline(style='width:218px;margin-right:0px;padding-top:9px;')
|
||||
select(name='pageId', required, lay-verify='required')
|
||||
option(value="") 请选择渠道...
|
||||
if fbMessengers
|
||||
for m in fbMessengers
|
||||
option(value=m.pageId)= m.name
|
||||
.layui-inline
|
||||
font(color='red') *(必填项)
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 订阅邀请信息(2000字符)
|
||||
.layui-colla-content.layui-show
|
||||
blockquote.layui-elem-quote.layui-quote-nm
|
||||
i.layui-icon(style="color:gray") 
|
||||
font(color="#999").layui-word-aux 访客点击链接后发送订阅邀请信息给访客
|
||||
.layui-form-item
|
||||
.layui-inline(style="width: 380px;")
|
||||
label.layui-form-label(style="float: none;text-align: left;width:200px")
|
||||
| 订阅邀请前消息1
|
||||
//font(color='red') *(必填项)
|
||||
.layui-input-inline
|
||||
#preSubMessage1.messageBox(name="preSubMessage1")
|
||||
.layui-inline(style="width: 380px;")
|
||||
label.layui-form-label(style="float: none;text-align: left;width:200px")
|
||||
| 订阅邀请前消息2
|
||||
//font(color='red') *(必填项)
|
||||
.layui-input-inline
|
||||
#preSubMessage2.messageBox(name="preSubMessage2")
|
||||
.layui-form-item
|
||||
.layui-inline(style="width: 320px;height: 209px;")
|
||||
label.layui-form-label(style="float: none;text-align: left;width:200px")
|
||||
| 订阅邀请
|
||||
font(color='red') *(必填项,60字符)
|
||||
.layui-input-inline
|
||||
textarea(name="subMessage", required="required", lay-verify="maxSubMessage", style="margin: 0px; width: 310px; height: 170px;resize:none;border: 1px solid #ccc")
|
||||
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 订阅成功提醒(2000字符)
|
||||
.layui-colla-content.layui-show
|
||||
.layui-input-inline
|
||||
.messageBox(name="successMessage")
|
||||
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 推送信息(2000字符)
|
||||
.layui-colla-content.layui-show
|
||||
blockquote.layui-elem-quote.layui-quote-nm
|
||||
i.layui-icon(style="color:gray") 
|
||||
font(color="#999").layui-word-aux 推送时间到达时,此内容通过OTN推送给访客。推送时间可以不设置,后续手动推送。
|
||||
.layui-form-item
|
||||
.layui-inline(style="width: 380px;")
|
||||
label.layui-form-label(style="float: none;text-align: left;width:200px") OTN内容
|
||||
.layui-input-inline
|
||||
.messageBox(name="otnMessage")
|
||||
.layui-inline(style="width: 320px;height: 209px;")
|
||||
label.layui-form-label(style="float: none;text-align: left;width:200px") 推送时间
|
||||
.layui-input-inline
|
||||
input#begin.layui-input.ukefu-input(name='sendtime', style="width:310px")
|
||||
|
||||
.layui-button-block(style="width: 700px;")
|
||||
button.layui-btn(lay-submit="", lay-filter="formDemo") 提交
|
||||
button.layui-btn.layui-btn-original(type="reset", onclick="location.reload()") 取消
|
||||
|
||||
script.
|
||||
layui.use('form', function () {
|
||||
var form = layui.form();
|
||||
form.render(); //更新全部
|
||||
|
||||
form.verify({
|
||||
maxSubMessage: function (value) {
|
||||
if (value.length > 50) {
|
||||
return '长度大于60!请重新输入';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
form.on('submit(formDemo)', function () {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
layui.use('laydate', function () {
|
||||
var laydate = layui.laydate;
|
||||
document.getElementById('begin').onclick = function () {
|
||||
var date = {
|
||||
min: laydate.now(),
|
||||
format: 'YYYY-MM-DD hh:mm:ss',
|
||||
istoday: false,
|
||||
istime: true
|
||||
};
|
||||
date.elem = this;
|
||||
laydate(date);
|
||||
}
|
||||
})
|
||||
|
||||
layui.use('element', function () {
|
||||
var element = layui.element();
|
||||
});
|
||||
|
||||
$(".messageBox").otnContent();
|
||||
|
||||
function preSubMessageChange() {
|
||||
var preSubMessage1 = JSON.parse($('#preSubMessage1 input').val() || '{}');
|
||||
var preSubMessage2 = JSON.parse($('#preSubMessage2 input').val() || '{}');
|
||||
|
||||
$('#preSubMessage').val(JSON.stringify([preSubMessage1, preSubMessage2]))
|
||||
}
|
||||
|
||||
$('#preSubMessage1').change(preSubMessageChange);
|
||||
$('#preSubMessage2').change(preSubMessageChange);
|
||||
|
||||
|
@ -0,0 +1,131 @@
|
||||
.row
|
||||
.col-lg-12
|
||||
.ukefu-customer-div.setting-wrapper
|
||||
.box.default-box
|
||||
.box-header
|
||||
h1.site-h1 编辑OTN
|
||||
|
||||
.row
|
||||
.col-lg-12
|
||||
.uk-layui-form(style="height: calc(100% + 10px);")
|
||||
form.layui-form(action="/apps/messenger/otn/update.html", method="post")
|
||||
input(type="hidden", name="pageId", value=pageId)
|
||||
input(type="hidden", name="id", value=otn.id)
|
||||
input#preSubMessage(type="hidden", name="preSubMessage", value=otn.preSubMessage)
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 基本信息
|
||||
.layui-colla-content.layui-show
|
||||
.layui-form-item
|
||||
.layui-inline
|
||||
label.layui-form-label 名称:
|
||||
.layui-input-inline
|
||||
input.layui-input(type='text', name='name', required, lay-verify='required', autocomplete='off', value=otn.name)
|
||||
.layui-inline
|
||||
font(color='red') *(必填项)
|
||||
.layui-inline
|
||||
label.layui-form-label(style='width:60px;line-height: 35px;') 渠道:
|
||||
.layui-input-inline(style='width:218px;margin-right:0px;padding-top:9px;')
|
||||
p(style="padding: 10px 0;")= otn.fbMessenger.name
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 订阅邀请信息(2000字符)
|
||||
.layui-colla-content.layui-show
|
||||
blockquote.layui-elem-quote.layui-quote-nm
|
||||
i.layui-icon(style="color:gray") 
|
||||
font(color="#999").layui-word-aux 访客点击链接后发送订阅邀请信息给访客
|
||||
.layui-form-item
|
||||
.layui-inline(style="width: 380px;")
|
||||
label.layui-form-label(style="float: none;text-align: left;width:200px")
|
||||
| 订阅邀请前消息1
|
||||
//font(color='red') *(必填项)
|
||||
.layui-input-inline
|
||||
#preSubMessage1.messageBox(name="preSubMessage1")
|
||||
.layui-inline(style="width: 380px;")
|
||||
label.layui-form-label(style="float: none;text-align: left;width:200px")
|
||||
| 订阅邀请前消息2
|
||||
//font(color='red') *(必填项)
|
||||
.layui-input-inline
|
||||
#preSubMessage2.messageBox(name="preSubMessage2")
|
||||
.layui-form-item
|
||||
.layui-inline(style="width: 320px;height: 209px;")
|
||||
label.layui-form-label(style="float: none;text-align: left;width:200px")
|
||||
| 订阅邀请
|
||||
font(color='red') *(必填项,65字符)
|
||||
.layui-input-inline
|
||||
textarea(name="subMessage", required="required", lay-verify="maxSubMessage", style="margin: 0px; width: 310px; height: 170px;resize:none;border: 1px solid #ccc")= otn.subMessage
|
||||
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 订阅成功提醒(2000字符)
|
||||
.layui-colla-content.layui-show
|
||||
.layui-input-inline
|
||||
.messageBox(name="successMessage",value=otn.successMessage)
|
||||
|
||||
.layui-colla-item
|
||||
h2.layui-colla-title 推送信息(2000字符)
|
||||
.layui-colla-content.layui-show
|
||||
blockquote.layui-elem-quote.layui-quote-nm
|
||||
i.layui-icon(style="color:gray") 
|
||||
font(color="#999").layui-word-aux 推送时间到达时,此内容通过OTN推送给访客。推送时间可以不设置,后续手动推送。
|
||||
.layui-form-item
|
||||
.layui-inline(style="width: 380px;")
|
||||
label.layui-form-label(style="float: none;text-align: left;width:200px") OTN内容
|
||||
.layui-input-inline
|
||||
.messageBox(name="otnMessage",value=otn.otnMessage)
|
||||
.layui-inline(style="width: 320px;height: 209px;")
|
||||
label.layui-form-label(style="float: none;text-align: left;width:200px") 推送时间
|
||||
.layui-input-inline
|
||||
input#begin.layui-input.ukefu-input(name='sendtime', style="width:310px", value=pugHelper.formatDate("yyyy-MM-dd HH:mm:ss", otn.sendtime))
|
||||
|
||||
.layui-button-block(style="width: 700px;")
|
||||
button.layui-btn(lay-submit="", lay-filter="formDemo") 提交
|
||||
button.layui-btn.layui-btn-original(type="reset", onclick="location.reload()") 取消
|
||||
|
||||
script.
|
||||
layui.use('form', function () {
|
||||
var form = layui.form();
|
||||
form.render(); //更新全部
|
||||
|
||||
form.verify({
|
||||
maxSubMessage: function (value) {
|
||||
if (value.length > 50) {
|
||||
return '长度大于60!请重新输入';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
form.on('submit(formDemo)', function () {
|
||||
|
||||
});
|
||||
});
|
||||
layui.use('element', function () {
|
||||
var element = layui.element();
|
||||
});
|
||||
|
||||
layui.use('laydate', function () {
|
||||
var laydate = layui.laydate;
|
||||
document.getElementById('begin').onclick = function () {
|
||||
var date = {
|
||||
min: laydate.now(),
|
||||
format: 'YYYY-MM-DD hh:mm:ss',
|
||||
istoday: false,
|
||||
istime: true
|
||||
};
|
||||
date.elem = this;
|
||||
laydate(date);
|
||||
}
|
||||
})
|
||||
|
||||
var preSubMessage = JSON.parse($('#preSubMessage').val() || '[{},{}]');
|
||||
$('#preSubMessage1').attr('value', JSON.stringify(preSubMessage[0]));
|
||||
$('#preSubMessage2').attr('value', JSON.stringify(preSubMessage[1]));
|
||||
|
||||
$(".messageBox").otnContent();
|
||||
|
||||
function preSubMessageChange() {
|
||||
var preSubMessage1 = JSON.parse($('#preSubMessage1 input').val() || '{}');
|
||||
var preSubMessage2 = JSON.parse($('#preSubMessage2 input').val() || '{}');
|
||||
|
||||
$('#preSubMessage').val(JSON.stringify([preSubMessage1, preSubMessage2]))
|
||||
}
|
||||
|
||||
$('#preSubMessage1').change(preSubMessageChange);
|
||||
$('#preSubMessage2').change(preSubMessageChange);
|
@ -0,0 +1,179 @@
|
||||
extends /apps/include/layout.pug
|
||||
|
||||
block append head
|
||||
script(src="/js/otnContent.js")
|
||||
|
||||
block content
|
||||
.layui-side.layui-bg-black
|
||||
.layui-side-scroll
|
||||
include /apps/marketing/left.pug
|
||||
#otn-edit-content.layui-body
|
||||
.layui-side-scroll
|
||||
.row
|
||||
input#copyInput(type="hidden")
|
||||
.col-lg-12
|
||||
.ukefu-customer-div.setting-wrapper
|
||||
.box.default-box
|
||||
.box-header
|
||||
h1.site-h1
|
||||
| Messenger OTN 列表
|
||||
blockquote.layui-elem-quote.layui-quote-nm
|
||||
i.layui-icon(style="color:gray") 
|
||||
font(color="#999").layui-word-aux 请勿使用负载字段发送密码、用户凭证、可识别用户身份的信息(即,姓名或邮箱等可单独用于联系用户或识别其身份的信息)或其他敏感信息(如健康状况、财务、支付或持卡人数据,或根据适用法律定义为敏感信息的其他类别的信息
|
||||
.row(style="padding:5px;")
|
||||
h1(style="width: 100%;")
|
||||
span(style="padding: 0 5px;") Messenger
|
||||
select#queryPageId(name='queryPageId')
|
||||
option(value) 请选择渠道...
|
||||
for m in fbMessengers
|
||||
option(value=m.pageId, selected=m.pageId == queryPageId)= m.name
|
||||
span(style="float:right;")
|
||||
button.layui-btn.layui-btn-small.green(href="/apps/messenger/otn/add.html", data-toggle="load", data-target="#otn-edit-content")
|
||||
| 创建OTN
|
||||
.row(style="padding:5px;")
|
||||
.col-lg-12
|
||||
table.layui-table(lay-skin="line", style="table-layout: fixed; word-break: break-all")
|
||||
thead
|
||||
tr
|
||||
th 名称
|
||||
th Messenger
|
||||
th(style="width:100px") 点击
|
||||
th(style="width:100px") 订阅
|
||||
th(style="width:80px") 创建时间
|
||||
th(style="width:80px") 发送时间
|
||||
th(style="width:60px") 状态
|
||||
th(style="width:60px") 分享链接
|
||||
th(style="width:180px;white-space:nowrap;", nowrap="nowrap") 操作
|
||||
tbody
|
||||
- var state = {'create' :'新建','sending':'发送中','finish':'已发送'}
|
||||
for item in otns.content
|
||||
tr
|
||||
td(title="", style="text-overflow: ellipsis;white-space: nowrap;overflow: hidden;")
|
||||
| #{item.name}
|
||||
td(title="", style="text-overflow: ellipsis;white-space: nowrap;overflow: hidden;")
|
||||
| #{item.fbMessenger.name}
|
||||
td(title="", style="text-overflow: ellipsis;white-space: nowrap;overflow: hidden;")
|
||||
| #{item.melinkNum}
|
||||
td(title="", style="text-overflow: ellipsis;white-space: nowrap;overflow: hidden;")
|
||||
| #{item.subNum}
|
||||
td
|
||||
| #{pugHelper.formatDate("yyyy-MM-dd HH:mm:ss", item.createtime)}
|
||||
td
|
||||
| #{item.sendtime ? pugHelper.formatDate("yyyy-MM-dd HH:mm:ss", item.sendtime) : ''}
|
||||
td(title="", style="text-overflow: ellipsis;white-space: nowrap;overflow: hidden;")
|
||||
| #{state[item.status]}
|
||||
td(style="text-overflow: ellipsis;white-space: nowrap;overflow: hidden;")
|
||||
button.layui-btn.layui-btn-small.webhook(type="button", data-id=item.id, data-page=item.pageId) 复制
|
||||
td(title="", style="text-overflow: ellipsis;white-space: nowrap;overflow: hidden;")
|
||||
if(item.status == 'create')
|
||||
a(href="/apps/messenger/otn/send.html?id=" + item.id, style="margin-left:10px;", data-toggle="tip", title="请确认是否发送?")
|
||||
i.layui-icon
|
||||
| 发送
|
||||
a(href="/apps/messenger/otn/edit.html?id=" + item.id, data-toggle="load", data-target="#otn-edit-content")
|
||||
i.layui-icon
|
||||
| 编辑
|
||||
a(href="/apps/messenger/otn/delete.html?id=" + item.id, style="margin-left:10px;", data-toggle="tip", title="请确认是否删除?")
|
||||
i.layui-icon(style="color:red;") ဆ
|
||||
| 删除
|
||||
.row(style='padding:5px;')
|
||||
.col-lg-12#page(style='text-align:center;')
|
||||
|
||||
script.
|
||||
layui.use(['laypage', 'layer'], function () {
|
||||
var laypage = layui.laypage,
|
||||
layer = layui.layer;
|
||||
laypage({
|
||||
cont: 'page',
|
||||
pages: #{ otns.totalPages }, //总页数
|
||||
curr: #{ otns.number + 1 },
|
||||
groups: 5, //连续显示分页数
|
||||
jump: function (data, first) {
|
||||
if (!first) {
|
||||
location.href = "/apps/messenger/otn/index.html?p=" + data.curr;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
layui.use('layer', function () {
|
||||
var layer = layui.layer;
|
||||
var msg = '#{msg}'
|
||||
if (msg == 'save_ok')
|
||||
layer.msg('OTN创建成功', {icon: 1, time: 1000})
|
||||
else if (msg == 'save_no_PageId')
|
||||
layer.msg('Page ID已存在', {icon: 2, time: 3000})
|
||||
else if (msg == 'send_ok')
|
||||
layer.msg('发送成功', {icon: 1, time: 3000})
|
||||
|
||||
});
|
||||
|
||||
$(function () {
|
||||
$('.webhook').each(function (i, n) {
|
||||
var id = $(n).data("id");
|
||||
var pageId = $(n).data("page");
|
||||
var webhook = "https://m.me/" + pageId + "?ref=" + id;
|
||||
$(n).on('click', function () {
|
||||
Clipboard.copy(webhook);
|
||||
})
|
||||
});
|
||||
|
||||
$('#queryPageId').change(function () {
|
||||
location.search = "queryPageId=" + $(this).val();
|
||||
})
|
||||
})
|
||||
|
||||
window.Clipboard = (function (window, document, navigator) {
|
||||
var textArea,
|
||||
copy;
|
||||
|
||||
// 判断是不是ios端
|
||||
function isOS() {
|
||||
return navigator.userAgent.match(/ipad|iphone/i);
|
||||
}
|
||||
|
||||
//创建文本元素
|
||||
function createTextArea(text) {
|
||||
textArea = document.createElement('textArea');
|
||||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
}
|
||||
|
||||
//选择内容
|
||||
function selectText() {
|
||||
var range,
|
||||
selection;
|
||||
if (isOS()) {
|
||||
range = document.createRange();
|
||||
range.selectNodeContents(textArea);
|
||||
selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
textArea.setSelectionRange(0, 999999);
|
||||
} else {
|
||||
textArea.select();
|
||||
}
|
||||
}
|
||||
|
||||
//复制到剪贴板
|
||||
function copyToClipboard() {
|
||||
try {
|
||||
if (document.execCommand("Copy")) {
|
||||
layer.msg('me link 已复制到剪切板', {icon: 1, time: 1000, offset: 'rt'})
|
||||
} else {
|
||||
layer.msg('me link 复制失败请手动复制', {icon: 2, time: 1000, offset: 'rt'})
|
||||
}
|
||||
} catch (err) {
|
||||
layer.msg('me link 复制失败请手动复制', {icon: 2, time: 1000, offset: 'rt'})
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
|
||||
copy = function (text) {
|
||||
createTextArea(text);
|
||||
selectText();
|
||||
copyToClipboard();
|
||||
};
|
||||
return {
|
||||
copy: copy
|
||||
};
|
||||
})(window, document, navigator);
|
@ -0,0 +1,86 @@
|
||||
block content
|
||||
include head
|
||||
|
||||
.layui-tab
|
||||
.layui-tab-content
|
||||
.layui-tab-item.layui-show
|
||||
form.layui-form(method='post', action="/admin/messenger/setting/save.html")
|
||||
input(type='hidden' name='id' value=fb.id)
|
||||
input.layui-input(type='checkbox', name='status', lay-skin='switch', lay-text="开启|关闭")
|
||||
.ukefu-customer-div.setting-wrapper
|
||||
.box.default-box
|
||||
.box-header
|
||||
h3.box-title 机器人客服
|
||||
.box-body(style="padding-top:5px;")
|
||||
.row: .col-lg-8
|
||||
.ukefu-webim-prop
|
||||
.ukefu-webim-tl(style="clear:both;") 1、转人工按钮文本
|
||||
.box-item
|
||||
.row
|
||||
.col-lg-8
|
||||
p 默认显示信息:转人工
|
||||
p(style="color:#888888;font-size:13px;margin-top:10px;") 访客在 Messenger 对话窗口,机器人客服转人工客服提示按钮文本
|
||||
.col-lg-4
|
||||
input.layui-input(type='text', name='transferManualService', lay-verify='required', value=(transferManualService ? transferManualService : "转人工"))
|
||||
|
||||
.ukefu-webim-prop
|
||||
.ukefu-webim-tl(style="clear:both;") 2、推荐问题提示文本
|
||||
.box-item
|
||||
.row
|
||||
.col-lg-8
|
||||
p 默认显示信息:您是否想问以下问题
|
||||
p(style="color:#888888;font-size:13px;margin-top:10px;") 机器人客服提示建议问题的文本
|
||||
.col-lg-4
|
||||
input.layui-input(type='text', name='suggestQuestion', lay-verify='required', value=(suggestQuestion ? suggestQuestion : "您是否想问以下问题"))
|
||||
|
||||
|
||||
.ukefu-webim-prop
|
||||
.ukefu-webim-tl(style="clear:both;") 3、询问是否有帮助文本
|
||||
.box-item
|
||||
.row
|
||||
.col-lg-8
|
||||
p 默认显示信息:以上答案是否对您有帮助
|
||||
p(style="color:#888888;font-size:13px;margin-top:10px;") 机器人客服询问回答是否有帮助的文本
|
||||
.col-lg-4
|
||||
input.layui-input(type='text', name='evaluationAsk', lay-verify='required', value=(evaluationAsk ? evaluationAsk : "以上答案是否对您有帮助"))
|
||||
.row: br
|
||||
.row
|
||||
.col-lg-8
|
||||
p 有帮助按钮文本
|
||||
p(style="color:#888888;font-size:13px;margin-top:10px;") 访客得到的上一条机器人回答有帮助
|
||||
.col-lg-4
|
||||
input.layui-input(type='text', name='evaluationYes', lay-verify='required', value=(evaluationYes ? evaluationYes : "是"))
|
||||
.row: br
|
||||
.row
|
||||
.col-lg-8
|
||||
p 有帮助后回复文本
|
||||
p(style="color:#888888;font-size:13px;margin-top:10px;") 访客反馈反馈有帮助后机器人回复,设置为空则不回复
|
||||
.col-lg-4
|
||||
input.layui-input(type='text', name='evaluationYesReply', lay-verify='required', value=(evaluationYesReply ? evaluationYesReply : "感谢您的反馈,我们会做的更好!"))
|
||||
|
||||
.row: br
|
||||
.row
|
||||
.col-lg-8
|
||||
p 无帮助按钮文本
|
||||
p(style="color:#888888;font-size:13px;margin-top:10px;") 访客得到的上一条机器人回答没帮助
|
||||
.col-lg-4
|
||||
input.layui-input(type='text', name='evaluationNo', lay-verify='required', value=(evaluationNo ? evaluationNo : "否"))
|
||||
.row: br
|
||||
.row
|
||||
.col-lg-8
|
||||
p 无帮助后回复文本
|
||||
p(style="color:#888888;font-size:13px;margin-top:10px;") 访客反馈反馈没帮助后机器人回复,设置为空则不回复
|
||||
.col-lg-4
|
||||
input.layui-input(type='text', name='evaluationNoReply', lay-verify='required', value=(evaluationNoReply ? evaluationNoReply : "感谢您的反馈,机器人在不断的学习!"))
|
||||
|
||||
.row
|
||||
.col-lg-3
|
||||
.col-lg-9
|
||||
.layui-form-item
|
||||
.layui-input-block
|
||||
button.layui-btn(lay-submit, lay-filter='formDemo') 保存
|
||||
button.layui-btn.layui-btn-original(type='reset', onclick="location.reload()") 取消
|
||||
script.
|
||||
layui.use('form', function () {
|
||||
var form = layui.form;
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user