1


目 标 场 景

我们都知道「闲鱼」的流量很大,除了淘宝、拼多多,很多商家和个人卖家都会在闲鱼上卖商品和各类服务。

闲鱼 App 内置的「自动回复」仅仅包含了一些固定关键字,包含默认回复、价格、包邮、发货地 4 个功能,因此,没法做到完全自定义消息回复。


另外,如果你有多个闲鱼号,经常会因为消息处理不及时,导致订单丢失了。因此,对闲鱼消息进行统一管理变得非常有必要。

本篇文章的目的是利用 上篇文章 提到的无障碍方案带大家实现这些功能。

2


实 现 代 码

首先,新建一个 Android 项目,然后在 app/src/main/res/xml/ 目录下新建配置文件。

利用 packageNames 属性指定仅监听闲鱼 App 的页面事件,accessibilityEventTypes 指定监听所有的事件,其他属性使用默认的即可。

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"android:accessibilityEventTypes="typeAllMask"android:accessibilityFeedbackType="feedbackGeneric"android:accessibilityFlags="flagDefault"android:canRetrieveWindowContent="true"android:description="@string/app_name"android:notificationTimeout="100"android:packageNames="com.taobao.idlefish" />

新建 AccessibilityService 的子类,在 AndroidManifest.xml 中配置服务后,然后来处理 onAccessibilityEvent() 事件方法。

public void onAccessibilityEvent(AccessibilityEvent event){}

无障碍服务会接受到很多种类的事件,这里我们只要处理下面 3 个事件,包含:通知栏事件、页面变换事件、页面内容变化事件。

# 通知栏事件
AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED
# 页面切换变化
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
# 页面内容变化
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED

所以,首先在 onAccessibilityEvent() 内需要对事件进行一次过滤,筛选出上面的 3 种事件。

//事件类型
int eventType = event.getEventType();
//获取包名
String packageName = event.getPackageName().toString();
//类名
String className = event.getClassName().toString();

//只筛选下面三个事件,其他事件过滤掉
if (eventType != AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED && eventType != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)
{
Log.e(“xxx”,“eventType:”+eventType);
return;
}

如果闲鱼应用在后台运行,这时如果收到一条闲鱼消息,会直接在通知栏展示出来,这个过程会触发「通知栏消息」事件。

一旦触发了通知栏消息事件,可以拿到通知栏的内容。

由于闲鱼消息通知文本没有实际的意义,这里通过通知对象拿到 Intent 对象,直接跳转到聊天界面。

/**** 处理通知栏消息* @param event*/
private void handleNotificationEventMet(AccessibilityEvent event)
{if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification){Notification notification = (Notification) event.getParcelableData();if (notification == null){return;}PendingIntent pendingIntent = notification.contentIntent;if (pendingIntent == null){return;}try{//注意:通知栏的文字消息没有参考意义//跳转到聊天信息界面Log.e("xag", "准备跳转到聊天界面");pendingIntent.send();} catch (PendingIntent.CanceledException e){e.printStackTrace();}}
}

跳转到聊天界面这一操作会立即触发「页面变更」事件,这里可以先将聊天界面滑到到底部。

/*** 模拟下滑操作*/
public void performScrollBackward()
{try{Thread.sleep(500);} catch (InterruptedException e){e.printStackTrace();}performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
}

需要获取元素的 ID 可以利用 DDMS 或者 uiautomatorviewer 两个工具。

通过观察,可以发现每一条聊天记录都在一个「列表元素」中。

/*** 查找对应ID的View  Level>=18** @param id id* @return View*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public AccessibilityNodeInfo findViewByID(String id)
{AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();if (accessibilityNodeInfo == null){return null;}List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);if (nodeInfoList != null && !nodeInfoList.isEmpty()){for (AccessibilityNodeInfo nodeInfo : nodeInfoList){if (nodeInfo != null){return nodeInfo;}}}return null;
}

//聊天列表
AccessibilityNodeInfo chat_lv = findViewByID(Ids.id_chat_listview);

通过列表元素,拿到所有子元素,然后遍历去查找「每一条聊天内容」元素。

聊天消息又包含发送消息、接受消息、公共消息(包含一直未回复?试试语音/视频通话) 3 种消息。

判断左、右图片是否存在,判断是哪种消息。如果左图标存在,就是接收到的消息;如果右图标存在,便是发送的消息;否则,就是公共消息。

int chat_count = chat_lv.getChildCount();

//注意:包含自己发送的消息、收到的消息、公共提示消息,比如:一直未回复?试试语音/视频通话
Log.e(TAG, “一共有:” + chat_count + “条聊天记录”);

/所有聊天记录
//系统聊天信息,可以忽略
List<String> chat_msgs_common = new ArrayList<>();
//收到的信息
List<String> chat_msgs_from = new ArrayList<>();
//发出去的信息
List<String> chat_msgs_to = new ArrayList<>();

for (int i = 0; i < chat_count; i++)
{

 AccessibilityNodeInfo tempChatAccessibilityNodeInfo = chat_lv.getChild(i);<span class="hljs-comment">//注意:通过判断是否有头像元素、昵称元素来判断是哪种聊天记录</span><span class="hljs-comment">//获取头像元素【左】【发送者】</span>AccessibilityNodeInfo headPortraitAccessibilityNodeInfoLeft = findViewByID(tempChatAccessibilityNodeInfo, Ids.id_chat_head_portrait_left);<span class="hljs-comment">//获取头像元素【右】【接受者】</span>AccessibilityNodeInfo headPortraitAccessibilityNodeInfoRight = findViewByID(tempChatAccessibilityNodeInfo, Ids.id_chat_head_portrait_right);<span class="hljs-comment">//获取文字内容</span>AccessibilityNodeInfo tempTextAccessibilityNodeInfo = findViewByID(tempChatAccessibilityNodeInfo, Ids.id_chat_text);<span class="hljs-keyword">if</span> (<span class="hljs-literal">null</span> == tempTextAccessibilityNodeInfo || <span class="hljs-literal">null</span> == tempTextAccessibilityNodeInfo.getText()){Log.e(TAG, <span class="hljs-string">"索引"</span> + i + <span class="hljs-string">",聊天内容为空"</span>);<span class="hljs-keyword">continue</span>;}String chatText = tempTextAccessibilityNodeInfo.getText().toString();Log.e(TAG, <span class="hljs-string">"聊天内容为:"</span> + chatText);<span class="hljs-keyword">if</span> (<span class="hljs-literal">null</span> != headPortraitAccessibilityNodeInfoLeft){chat_msgs_from.<span class="hljs-keyword">add</span>(<span class="hljs-number">0</span>, chatText);} <span class="hljs-function"><span class="hljs-keyword">else</span> <span class="hljs-title">if</span> (<span class="hljs-params"><span class="hljs-literal">null</span> != headPortraitAccessibilityNodeInfoRight</span>)</span>{chat_msgs_to.<span class="hljs-keyword">add</span>(<span class="hljs-number">0</span>, chatText);} <span class="hljs-keyword">else</span>{chat_msgs_common.<span class="hljs-keyword">add</span>(<span class="hljs-number">0</span>, chatText);}

}

回复消息」可以拿到底部的输入框,把文本传入到输入框,模拟点击右侧发送按钮就能够实现。

/**** 回复消息* @param event*/
private void reply_content(AccessibilityEvent event, String content)
{//元素:输入框AccessibilityNodeInfo chat_edit = findViewByID(Ids.id_edittext);
<span class="hljs-comment">//把文本输入进去</span>
inputText(chat_edit, content);<span class="hljs-comment">//元素:发送按钮</span>
AccessibilityNodeInfo chat_send = findViewByID(Ids.id_sendtext);Log.e(TAG,<span class="hljs-string">"准备回复的内容是:"</span>+content);<span class="hljs-keyword">try</span>
{Thread.sleep(<span class="hljs-number">5000</span>);
} <span class="hljs-keyword">catch</span> (InterruptedException e)
{e.printStackTrace();
}
<span class="hljs-comment">//模拟点击发送按钮</span>
performViewClick(chat_send);

}

根据收到的消息数目判断是否是「第一次收到的消息」。

如果是第一次收到消息,就可以返回一些引导语,告诉客户可以通过回复固定的内容返回对应的消息;否则拿到最新的一条消息,回复自定义的内容。

ps:这部分内容可以完全自定义处理。

//定义一段固定的回复内容
public static String reply_first = "让我开心的是,可以在这里遇见有趣的事物,可以遇见您。\n有什么可以帮到您的呢?\n回复【11】获取商品信息\n回复【22】获取发货信息";

//11:商品信息
public static String reply_11 = “亲,商品还有货\n现在可以直接拍下\n等店主看到了会第一时间发货”;

//22:发货信息
public static String reply_22 = “亲,我们是包邮的!\n需要邮寄快递可以给我们留言”;

//other:其他
public static String reply_other = “亲,信息收到了!\n主人会第一时间给你答复,稍等哈~”;

// 初次聊天就发送默认的信息
if (chat_msgs_from.size() == 1 && chat_msgs_to.size() == 0)
{
//输入之后,返回,退出输入框
Log.e(TAG, “第一次收到信息,回复默认准备的内容”);
reply_content(event, Constants.reply_first);

} else if (chat_msgs_from.size() > 0)
{
//第一条文本内容
String first_msg = chat_msgs_from.get(0);
if (“11”.equals(first_msg))
{
reply_content(event, Constants.reply_11);
} else if (“22”.equals(first_msg))
{
reply_content(event, Constants.reply_22);
} else
{
reply_content(event, Constants.reply_other);
}

} else
{
Log.e(TAG, “对方没有应答,不处理”);
}

消息回复完成后,需要模拟点击返回键,直到页面回到应用的主界面。

/*** 模拟返回操作*/
public void performBackClick()
{try{Thread.sleep(500);} catch (InterruptedException e){e.printStackTrace();}
<span class="hljs-keyword">while</span> (!AppUtils.judgeIsMainPage(getRootInActiveWindow()))
{Log.e(<span class="hljs-string">"xag"</span>,<span class="hljs-string">"现在不是主页面,返回一次"</span>);performGlobalAction(GLOBAL_ACTION_BACK);
}

}

当回到主界面,这时候如有收到新的消息,聊天主界面会触发「页面内容变化」事件。


可以先判断消息列表是否存在「未读」的消息元素。

/**** 获取消息列表中有未读的消息Node* @param node* @return*/
public static AccessibilityNodeInfo getUnreadMsgNode(AccessibilityNodeInfo node)
{AccessibilityNodeInfo unread_node = null;
<span class="hljs-keyword">if</span> (node == <span class="hljs-literal">null</span> || node.getChildCount() &lt;= <span class="hljs-number">1</span>)
{Log.e(<span class="hljs-string">"xag"</span>, <span class="hljs-string">"未读消息判断,node==null"</span>);<span class="hljs-keyword">return</span> unread_node;
}Log.e(<span class="hljs-string">"xag"</span>, <span class="hljs-string">"未读消息判断,子类个数:"</span> + node.getChildCount());<span class="hljs-comment">//过滤ListView的头部,包含:通知消息、互动消息、活动消息、鱼塘消息</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">1</span>; i &lt; node.getChildCount(); i++)
{AccessibilityNodeInfo temp_node = node.getChild(i);List&lt;AccessibilityNodeInfo&gt; unread_nodes = temp_node.findAccessibilityNodeInfosByViewId(Ids.id_main_unread_tag);<span class="hljs-keyword">if</span> (<span class="hljs-literal">null</span> == unread_nodes || unread_nodes.size() == <span class="hljs-number">0</span>){Log.e(<span class="hljs-string">"xag"</span>, <span class="hljs-string">"未读消息判断,索引:"</span> + i + <span class="hljs-string">",unread_nodes为空"</span>);} <span class="hljs-keyword">else</span>{unread_node = unread_nodes.<span class="hljs-keyword">get</span>(<span class="hljs-number">0</span>);<span class="hljs-keyword">break</span>;}
}Log.e(<span class="hljs-string">"xag"</span>, <span class="hljs-string">"未读消息判断,unread_node==null:"</span> + (unread_node == <span class="hljs-literal">null</span>));<span class="hljs-keyword">return</span> unread_node;

}

如果应用「主页面聊天列表界面」存在未读的消息,就点击进入到聊天界面,重复利用上面的逻辑就可以回复消息了。

//选中在消息Tab,处理聊天页面
if (Constants.HOME_TAB.TAB_XIAO_XI == currentTab)
{Log.e(TAG, "当前Tab:消息Tab");//判断新消息在哪个位置,然后点击进入AccessibilityNodeInfo main_listview_node = findViewByID(Ids.id_main_conversation_listview);
<span class="hljs-comment">//未读消息Node</span>
AccessibilityNodeInfo unread_tag_node = AppUtils.getUnreadMsgNode(main_listview_node);<span class="hljs-keyword">if</span> (<span class="hljs-literal">null</span> != unread_tag_node)
{Log.e(TAG, <span class="hljs-string">"点击进入会话"</span>);<span class="hljs-comment">//点击进入消息列表</span>performViewClick(unread_tag_node);<span class="hljs-comment">//处理聊天信息</span>handleChatMet(<span class="hljs-keyword">event</span>);} <span class="hljs-keyword">else</span>{Log.e(TAG, <span class="hljs-string">"列表中没有未读消息"</span>);}

}

自动化篇 - 为闲鱼制作一个客服机器人相关推荐

  1. 自动化篇 - 为闲鱼制作一个客服机器人(上)

    点击上方"AirPython",选择"置顶公众号" 第一时间获取 Python 技术干货! 阅读文本大概需要 18 分钟. 1 目 标 场 景 我们都知 道 「 ...

  2. php客服窗口,制作一个客服小界面

    摘要: &实战 div:nth-child(1){ width: 450px; height: 650px; background-color: lightblue; margin: 30px ...

  3. 网易七鱼“大闹”客服行业,真能一举定乾坤?

    自2015年以来,网易在云计算领域就不断发力,网易云信.网易视频云.网易蜂巢等产品相继推出,作为网易云推出的首款运营云,也是网易云的重要布局的网易七鱼在今年4月正式对外发布后,又于11月29日在北京举 ...

  4. 智能客服机器人的优势,促进客户服务自动化

    近年来人工智能已渗入工作生活中,由于AI智能技术越来越成熟,智能客服机器人已经成为客服系统发展的一种趋势,客服智能化也已经超过机器人自动回复的范畴,渗透于整个客服工作中.使客户能够更加便捷高效地进行自 ...

  5. 用arduino和16路舵机控制板制作一个蛇形仿生机器人

    要制作一个蛇形仿生机器人,需要使用Arduino和16路舵机控制板.首先需要设计机器人的结构并确定舵机的位置,然后使用Arduino编写程序来控制舵机的运动.在编写程序时,需要考虑机器人的运动学和力学 ...

  6. 阿里旺旺分析系列一:实时获取阿里旺旺聊天消息,实现旺旺客服机器人

    目前网络上有为数不多的几款客服机器人.比如某某旺旺机器人.实现的大概机制是获取淘宝旺旺收到的聊天消息.从本地数据库中搜索答案.然后再自动或者手动回复. 本文详细讲述如何分析阿里旺旺和某某旺旺机器人软件 ...

  7. 网页客服机器人_易聊AI客服机器人强大线索获取能力助力企业稳操胜券

    移动互联网时代,各类的APP.网站等线上平台是诸多企业获取客户的重要来源,为了更好地收集潜在客户线索,为数众多的企业都采用了AI客服机器人.来自市场的数据显示,尽管这类机器人系统数量庞大,但是他们在获 ...

  8. 当Elasticsearch遇见智能客服机器人

    摘要 本次分享主要会介绍一下ES是如何帮我们完成NLP的任务的.在做NLP相关任务的时候,ES的相似度算法并不足以支撑用户的搜索,需要使用一些与语义相关的方法进行改进.但是ES的很多特性对我们优化搜索 ...

  9. 【Tensorflow+自然语言处理+LSTM】搭建智能聊天客服机器人实战(附源码、数据集和演示 超详细)

    需要源码和数据集请点赞关注收藏后评论区留言私信~~~ 一.自然语言处理与智能 自然语言处理技术是智能客服应用的基础,在自然语言处理过程中,首先需要进行分词处理,这个过程通常基于统计学理论,分词的精细化 ...

最新文章

  1. 涉密文件检查工具_肇庆高要销毁资料文件公司粉碎销毁文件资料公司欢迎您
  2. soso街景开发——在移动应用(网站)中的应用
  3. go读取excel_Excelize 2.3.0 发布,Go 语言 Excel 文档基础库
  4. fatal error C1010: unexpected end of file while...
  5. Node.js 替换文档内容
  6. [汇编与C语言关系]1.函数调用
  7. Android客户端实现session会话过期的功能
  8. matlab 3.BPF封装 巴特沃斯带通滤波器
  9. 什么可以代替pencil?pencil代替品推荐
  10. 为什么我会感到迷茫? 文/江湖一剑客
  11. 【SwiftUI模块】0012、SwiftUI-搭建一个类似微博、网易云、抖音个人页面的头部下拉放大图片效果
  12. Date.now()和new.Date().getTime()的区别
  13. # CSS 背景设置
  14. 朴素贝叶斯法的参数估计——贝叶斯估计及其Python实现
  15. 【点云系列】基于图结构的点云快速重采样 翻译
  16. 【str家族】如何使用处理字符和字符串的库函数
  17. 20145203盖泽双 《网络对抗技术》实践七:网络欺诈技术防范
  18. 招沿实业学生怎样才能做好投资理财工作
  19. Docker Networking Docker 网络设置
  20. Infocom2022视频相关文章

热门文章

  1. 《Java从入门到项目实战(全程视频版)》(李兴华 著)【配套资源及赠送资源】
  2. Bugku-CTF (web 持续更新) ——新手ctf记录
  3. Android大话设计模式 第三章----开放封闭原则---孙悟空任弼马温一职
  4. 没有专业技能不要紧,通过快营通也能月入上千元
  5. 区块链P2P网络协议演进过程
  6. 自动驾驶的理想破灭?我看到的这些场景都是噩梦 | 分析
  7. 中兴网卡连不上网,解决方案如下
  8. 蜂鸣器的结构原理及制作
  9. 《左耳听风》-ARTS-打卡记录-第十四周
  10. Java初学者快速上手之实战“套路”