From 01063dc6d35a994446abf0765f627cdf5a9be194 Mon Sep 17 00:00:00 2001 From: gallonyin Date: Thu, 10 Nov 2022 11:22:53 +0800 Subject: [PATCH] =?UTF-8?q?update=20root&xp=E6=A3=80=E6=B5=8B=E6=8F=90?= =?UTF-8?q?=E7=A4=BA;=E4=BB=8E=E5=A4=96=E9=83=A8=E7=BE=A4=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=A5=BD=E5=8F=8B;=E4=BF=AE=E6=94=B9=E5=A5=BD?= =?UTF-8?q?=E5=8F=8B=E4=BF=A1=E6=81=AF;=E5=BB=BA=E7=BE=A4=E8=BE=BE?= =?UTF-8?q?=E5=88=B0=E4=B8=8A=E9=99=90=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/yameida/worktool/Constant.kt | 1 + .../worktool/activity/ListenActivity.kt | 16 +- .../worktool/model/WeworkMessageBean.java | 17 + .../yameida/worktool/service/GlobalMethod.kt | 2 +- .../org/yameida/worktool/service/MyLooper.kt | 11 +- .../worktool/service/WeworkController.kt | 18 +- .../worktool/service/WeworkOperationImpl.kt | 419 ++++++++++++++---- .../yameida/worktool/service/WeworkService.kt | 6 +- .../worktool/utils/envcheck/CheckHook.java | 110 +++++ .../worktool/utils/envcheck/CheckRoot.java | 221 +++++++++ 10 files changed, 736 insertions(+), 85 deletions(-) create mode 100644 app/src/main/java/org/yameida/worktool/utils/envcheck/CheckHook.java create mode 100644 app/src/main/java/org/yameida/worktool/utils/envcheck/CheckRoot.java diff --git a/app/src/main/java/org/yameida/worktool/Constant.kt b/app/src/main/java/org/yameida/worktool/Constant.kt index 91675bd..c6ac418 100644 --- a/app/src/main/java/org/yameida/worktool/Constant.kt +++ b/app/src/main/java/org/yameida/worktool/Constant.kt @@ -19,6 +19,7 @@ object Constant { val transformation = "AES/CBC/PKCS7Padding" var encryptType = SPUtils.getInstance().getInt("encryptType", 1) var autoReply = SPUtils.getInstance().getInt("autoReply", 1) + var groupStrict = false var host: String get() = SPUtils.getInstance().getString("host", DEFAULT_HOST) set(value) { diff --git a/app/src/main/java/org/yameida/worktool/activity/ListenActivity.kt b/app/src/main/java/org/yameida/worktool/activity/ListenActivity.kt index 49122e0..b9eccb3 100644 --- a/app/src/main/java/org/yameida/worktool/activity/ListenActivity.kt +++ b/app/src/main/java/org/yameida/worktool/activity/ListenActivity.kt @@ -21,6 +21,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.yameida.worktool.utils.HostTestHelper import org.yameida.worktool.utils.PermissionHelper import org.yameida.worktool.utils.PermissionPageManagement +import org.yameida.worktool.utils.envcheck.CheckHook +import org.yameida.worktool.utils.envcheck.CheckRoot class ListenActivity : AppCompatActivity() { @@ -87,7 +89,15 @@ class ListenActivity : AppCompatActivity() { true } val version = "${AppUtils.getAppVersionName()} Android ${DeviceUtils.getSDKVersionName()} ${DeviceUtils.getManufacturer()} ${DeviceUtils.getModel()}" - tv_version.text = version + val deviceRooted = CheckRoot.isDeviceRooted() + val hook = CheckHook.isHook(applicationContext) + if (hook) { + tv_version.text = "当前设备存在侵入代码,请勿在本设备使用本程序!!!" + } else if (deviceRooted) { + tv_version.text = "$version\n本设备已Root,存在一定风险!" + } else { + tv_version.text = version + } val workVersionName = AppUtils.getAppInfo(Constant.PACKAGE_NAMES)?.versionName when (workVersionName) { null -> { @@ -107,6 +117,8 @@ class ListenActivity : AppCompatActivity() { } SPUtils.getInstance().put("appVersion", version) SPUtils.getInstance().put("workVersion", workVersionName) + SPUtils.getInstance().put("deviceRooted", deviceRooted) + SPUtils.getInstance().put("hook", hook) } private fun initAccessibility() { @@ -215,7 +227,7 @@ class ListenActivity : AppCompatActivity() { if (text.matches("ws{1,2}://[^/]+.*".toRegex())) { Constant.host = text tv_host.text = text - HostTestHelper.test() + HostTestHelper.testWs() commentDialog.dismiss() } else { ToastUtils.showLong("格式异常!") diff --git a/app/src/main/java/org/yameida/worktool/model/WeworkMessageBean.java b/app/src/main/java/org/yameida/worktool/model/WeworkMessageBean.java index a55db60..354267d 100644 --- a/app/src/main/java/org/yameida/worktool/model/WeworkMessageBean.java +++ b/app/src/main/java/org/yameida/worktool/model/WeworkMessageBean.java @@ -32,6 +32,8 @@ public class WeworkMessageBean { * 按手机号添加好友 ADD_FRIEND_BY_PHONE * 展示群信息 SHOW_GROUP_INFO * 推送文件(网络图片视频和文件等) PUSH_FILE + * 解散群聊 DISMISS_GROUP + * 从外部群添加好友 ADD_FRIEND_BY_GROUP *

* 非操作类型 300 * 机器人普通日志记录 ROBOT_LOG @@ -66,6 +68,7 @@ public class WeworkMessageBean { public static final int DELETED_ANTI_HARASSMENT_RULE = 217; public static final int PUSH_FILE = 218; public static final int DISMISS_GROUP = 219; + public static final int ADD_FRIEND_BY_GROUP = 220; public static final int ROBOT_LOG = 301; public static final int ROBOT_ERROR_LOG = 302; @@ -287,6 +290,20 @@ public class WeworkMessageBean { public int hashCode() { return Objects.hash(phone, name, markName, markCorp, markExtra, tagList, newFriend, leavingMsg); } + + @Override + public String toString() { + return "Friend{" + + "phone='" + phone + '\'' + + ", name='" + name + '\'' + + ", markName='" + markName + '\'' + + ", markCorp='" + markCorp + '\'' + + ", markExtra='" + markExtra + '\'' + + ", tagList=" + tagList + + ", newFriend=" + newFriend + + ", leavingMsg='" + leavingMsg + '\'' + + '}'; + } } @Override diff --git a/app/src/main/java/org/yameida/worktool/service/GlobalMethod.kt b/app/src/main/java/org/yameida/worktool/service/GlobalMethod.kt index 9ea43e7..dc7e4d6 100644 --- a/app/src/main/java/org/yameida/worktool/service/GlobalMethod.kt +++ b/app/src/main/java/org/yameida/worktool/service/GlobalMethod.kt @@ -117,7 +117,7 @@ fun getRoot(ignoreCheck: Boolean): AccessibilityNodeInfo { } } } - sleep(1000) + sleep(Constant.CHANGE_PAGE_INTERVAL) } } diff --git a/app/src/main/java/org/yameida/worktool/service/MyLooper.kt b/app/src/main/java/org/yameida/worktool/service/MyLooper.kt index 39d4b0d..5342e05 100644 --- a/app/src/main/java/org/yameida/worktool/service/MyLooper.kt +++ b/app/src/main/java/org/yameida/worktool/service/MyLooper.kt @@ -136,16 +136,19 @@ object MyLooper { WeworkMessageBean.PUSH_OFFICE -> { WeworkController.pushOffice(message) } + WeworkMessageBean.PASS_ALL_FRIEND_REQUEST -> { + } + WeworkMessageBean.ADD_FRIEND_BY_PHONE -> { + WeworkController.addFriendByPhone(message) + } WeworkMessageBean.PUSH_FILE -> { WeworkController.pushFile(message) } WeworkMessageBean.DISMISS_GROUP -> { WeworkController.dismissGroup(message) } - WeworkMessageBean.PASS_ALL_FRIEND_REQUEST -> { - } - WeworkMessageBean.ADD_FRIEND_BY_PHONE -> { - WeworkController.addFriendByPhone(message) + WeworkMessageBean.ADD_FRIEND_BY_GROUP -> { + WeworkController.addFriendByGroup(message) } WeworkMessageBean.SHOW_GROUP_INFO -> { WeworkController.showGroupInfo(message) diff --git a/app/src/main/java/org/yameida/worktool/service/WeworkController.kt b/app/src/main/java/org/yameida/worktool/service/WeworkController.kt index cb5659a..c952088 100644 --- a/app/src/main/java/org/yameida/worktool/service/WeworkController.kt +++ b/app/src/main/java/org/yameida/worktool/service/WeworkController.kt @@ -187,6 +187,22 @@ object WeworkController { ) } + /** + * 从外部群添加好友 + * @see WeworkMessageBean.ADD_FRIEND_BY_GROUP + * @param message#groupName 外部群 + * @param message#friend 待添加用户 + */ + @RequestMapping + fun addFriendByGroup(message: WeworkMessageBean): Boolean { + LogUtils.d("addFriendByGroup(): ${message.groupName} ${message.friend}") + return WeworkOperationImpl.addFriendByGroup( + message, + message.groupName, + message.friend + ) + } + /** * 推送微盘图片 * @see WeworkMessageBean.PUSH_MICRO_DISK_IMAGE @@ -287,7 +303,7 @@ object WeworkController { /** * 按手机号添加好友 * @see WeworkMessageBean.ADD_FRIEND_BY_PHONE - * @param message#friend 待添加用户列表 + * @param message#friend 待添加用户 */ @RequestMapping fun addFriendByPhone(message: WeworkMessageBean): Boolean { diff --git a/app/src/main/java/org/yameida/worktool/service/WeworkOperationImpl.kt b/app/src/main/java/org/yameida/worktool/service/WeworkOperationImpl.kt index cbcd525..bc756f2 100644 --- a/app/src/main/java/org/yameida/worktool/service/WeworkOperationImpl.kt +++ b/app/src/main/java/org/yameida/worktool/service/WeworkOperationImpl.kt @@ -219,13 +219,19 @@ object WeworkOperationImpl { uploadCommandResult(message, ExecCallbackBean.ERROR_CREATE_GROUP, "创建群失败", startTime) return false } + if (createGroupLimit()) { + uploadCommandResult(message, ExecCallbackBean.ERROR_CREATE_GROUP_LIMIT, "建群达到上限", startTime) + return false + } else { + LogUtils.v("未发现建群达到上限") + } } if (!groupRename(groupName)) { uploadCommandResult(message, ExecCallbackBean.ERROR_GROUP_RENAME, "创建群成功 群改名失败", startTime) return false } if (!groupAddMember(selectList)) { - uploadCommandResult(message, ExecCallbackBean.ERROR_GROUP_ADD_MEMBER, "创建群成功 群改名成功 群拉人失败 ${selectList?.joinToString()}", startTime) + uploadCommandResult(message, ExecCallbackBean.ERROR_GROUP_ADD_MEMBER, "创建群成功 群改名成功 群拉人失败: ${selectList?.joinToString()}", startTime) return false } if (!groupChangeAnnouncement(groupAnnouncement)) { @@ -278,11 +284,11 @@ object WeworkOperationImpl { return false } if (!groupAddMember(selectList, showMessageHistory == true)) { - uploadCommandResult(message, ExecCallbackBean.ERROR_GROUP_ADD_MEMBER, "进入房间成功 群改名成功 群拉人失败 ${selectList?.joinToString()}", startTime) + uploadCommandResult(message, ExecCallbackBean.ERROR_GROUP_ADD_MEMBER, "进入房间成功 群改名成功 群拉人失败: ${selectList?.joinToString()}", startTime) return false } if (!groupRemoveMember(removeList)) { - uploadCommandResult(message, ExecCallbackBean.ERROR_GROUP_REMOVE_MEMBER, "进入房间成功 群改名成功 群拉人成功 群踢人失败 ${removeList?.joinToString()}", startTime) + uploadCommandResult(message, ExecCallbackBean.ERROR_GROUP_REMOVE_MEMBER, "进入房间成功 群改名成功 群拉人成功 群踢人失败: ${removeList?.joinToString()}", startTime) return false } if (!groupChangeAnnouncement(newGroupAnnouncement)) { @@ -657,15 +663,69 @@ object WeworkOperationImpl { } /** - * 手机号添加好友 + * 手机号添加好友或修改好友信息 * @see WeworkMessageBean.ADD_FRIEND_BY_PHONE - * @param friend 待添加用户列表 + * @param friend 待添加用户 */ fun addFriendByPhone( message: WeworkMessageBean, friend: WeworkMessageBean.Friend ): Boolean { val startTime = System.currentTimeMillis() + //如果已经是好友的可以传name修改好友信息 + if (friend.phone == null && friend.name != null) { + if (getFriendInfo(friend.name)) { + if (AccessibilityUtil.findOneByText(getRoot(), "标签", "电话", "描述", "设置备注和描述", exact = true) != null) { + var markTv = + AccessibilityUtil.findOnceByText(getRoot(), "设置备注和描述", exact = true) + if (markTv == null) { + markTv = AccessibilityUtil.findOnceByText(getRoot(), "企业", exact = true) + } + if (markTv == null) { + markTv = AccessibilityUtil.findOnceByText(getRoot(), "描述", exact = true) + } + //设置备注 + if (markTv != null && (friend.markName != null + || friend.markCorp != null || friend.markExtra != null) + ) { + AccessibilityUtil.performClick(markTv) + val etList = + AccessibilityUtil.findAllByClazz(getRoot(), Views.EditText, minSize = 5) + if (etList.size >= 5) { + if (friend.markName != null) { + AccessibilityUtil.editTextInput(etList[0], friend.markName) + } + if (friend.markCorp != null) { + AccessibilityUtil.editTextInput(etList[1], friend.markCorp) + } + if (friend.markExtra != null) { + AccessibilityUtil.editTextInput(etList[4], friend.markExtra) + } + } + AccessibilityUtil.findTextAndClick(getRoot(), "保存") + } + //设置标签 + if (!friend.tagList.isNullOrEmpty()) { + if (AccessibilityUtil.findTextAndClick(getRoot(), "标签")) { + setFriendTags(friend.tagList) + } + } + } else { + LogUtils.e("未找到标签") + uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "未找到标签", startTime) + return false + } + LogUtils.d("修改好友信息成功: ${friend.name}") + uploadCommandResult(message, ExecCallbackBean.SUCCESS, "", startTime) + sleep(3000) + return true + } else { + LogUtils.e("未找到用户: ${friend.name}") + uploadCommandResult(message, ExecCallbackBean.ERROR_TARGET, "未找到用户: ${friend.name}", startTime) + return false + } + } + //手机号添加好友 goHome() val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.RecyclerView, Views.ListView, Views.ViewGroup) if (list != null) { @@ -685,7 +745,8 @@ object WeworkOperationImpl { AccessibilityUtil.findTextAndClick(getRoot(), "搜索手机号添加") AccessibilityUtil.findTextInput(getRoot(), friend.phone.trim()) if (AccessibilityUtil.findTextAndClick(getRoot(), "网络查找手机")) { - val bothUsedTv = AccessibilityUtil.findOneByText(getRoot(), "对方同时使用", "标签", "电话") + sleep(Constant.POP_WINDOW_INTERVAL) + val bothUsedTv = AccessibilityUtil.findOneByTextRegex(getRoot(), "(对方同时使用.*?)|(标签)|(电话)|(描述)|(设置备注和描述)", timeout = 2000) val bothUsedText = bothUsedTv?.text if (bothUsedText != null && bothUsedText.contains("对方同时使用")) { AccessibilityUtil.performClick( @@ -695,62 +756,14 @@ object WeworkOperationImpl { ) ) } - if (AccessibilityUtil.findOneByText(getRoot(), "标签", "电话") != null) { - var markTv = AccessibilityUtil.findOnceByText(getRoot(), "设置备注和描述", exact = true) - if (markTv == null) { - markTv = AccessibilityUtil.findOnceByText(getRoot(), "企业", exact = true) - } - if (markTv == null) { - markTv = AccessibilityUtil.findOnceByText(getRoot(), "描述", exact = true) - } - //设置备注 - if (markTv != null && (friend.markName != null - || friend.markCorp != null || friend.markExtra != null) - ) { - AccessibilityUtil.performClick(markTv) - val etList = - AccessibilityUtil.findAllByClazz(getRoot(), Views.EditText, minSize = 5) - if (etList.size >= 5) { - if (friend.markName != null) { - AccessibilityUtil.editTextInput(etList[0], friend.markName) - } - if (friend.markCorp != null) { - AccessibilityUtil.editTextInput(etList[1], friend.markCorp) - } - if (friend.markExtra != null) { - AccessibilityUtil.editTextInput(etList[4], friend.markExtra) - } - } - AccessibilityUtil.findTextAndClick(getRoot(), "保存") - } - //设置标签 - if (!friend.tagList.isNullOrEmpty()) { - if (AccessibilityUtil.findTextAndClick(getRoot(), "标签")) { - setFriendTags(friend.tagList) - } - } - //添加联系人 - val imageView = - AccessibilityUtil.findOneByClazz(getRoot(), Views.ImageView) - if (imageView != null) { - val textViewList = AccessibilityUtil.findAllOnceByClazz( - imageView.parent, - Views.TextView - ) - val filter = - textViewList.filter { it.text != null && it.text.toString() != "微信" } - if (filter.isNotEmpty()) { - val tvNick = filter[0] - LogUtils.d("好友昵称或备注名: " + tvNick.text) - } - } + if (modifyFriendInfo(friend)) { if (AccessibilityUtil.findTextAndClick(getRoot(), "添加为联系人")) { - LogUtils.d("准备发送好友邀请: " + friend.phone) + LogUtils.d("准备发送好友邀请: ${friend.phone}") if (!friend.leavingMsg.isNullOrEmpty()) { AccessibilityUtil.findTextInput(getRoot(), friend.leavingMsg) } if (AccessibilityUtil.findTextAndClick(getRoot(), "发送添加邀请", "发送申请")) { - LogUtils.d("发送添加邀请成功: " + friend.phone) + LogUtils.d("发送添加邀请成功: ${friend.phone}") uploadCommandResult(message, ExecCallbackBean.SUCCESS, "", startTime) return true } else { @@ -770,8 +783,8 @@ object WeworkOperationImpl { } } } else { - LogUtils.e("未找到标签") - uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "未找到标签", startTime) + LogUtils.e("修改用户信息失败: ${friend.phone}") + uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "修改用户信息失败: ${friend.phone}", startTime) return false } } else { @@ -801,6 +814,119 @@ object WeworkOperationImpl { } } + /** + * 从外部群添加好友 + * @see WeworkMessageBean.ADD_FRIEND_BY_PHONE + * @param groupName 外部群 + * @param friend 待添加用户 + */ + fun addFriendByGroup( + message: WeworkMessageBean, + groupName: String, + friend: WeworkMessageBean.Friend + ): Boolean { + val startTime = System.currentTimeMillis() + if (WeworkRoomUtil.intoRoom(groupName) && WeworkRoomUtil.intoGroupManager()) { + if (AccessibilityUtil.findTextAndClick(getRoot(), "查看全部群成员")) { + val title = friend.name + val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) + if (list != null) { + val frontNode = AccessibilityUtil.findFrontNode(list) + val textViewList = AccessibilityUtil.findAllOnceByClazz(frontNode, Views.TextView) + .filter { it.text == null } + if (textViewList.size >= 2) { + val searchButton: AccessibilityNodeInfo = textViewList[textViewList.size - 1] + AccessibilityUtil.performClick(searchButton) + val needTrim = title.contains(Constant.regTrimTitle) + val trimTitle = title.replace(Constant.regTrimTitle, "") + AccessibilityUtil.findTextInput(getRoot(), trimTitle) + sleep(Constant.CHANGE_PAGE_INTERVAL) + //消息页搜索结果列表 + val selectListView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) + val reverseRegexTitle = RegexHelper.reverseRegexTitle(trimTitle) + val regex1 = "^$reverseRegexTitle" + if (needTrim) ".*?" else "(-.*)?(…)?(\\(.*?\\))?$" + val regex2 = ".*?\\($reverseRegexTitle\\)$" + val regex = "($regex1)|($regex2)" + val matchSelect = AccessibilityUtil.findOneByTextRegex( + selectListView, + regex, + timeout = 2000, + root = false + ) + if (selectListView != null && matchSelect != null) { + for (i in 0 until selectListView.childCount) { + val item = selectListView.getChild(i) + val searchResult = AccessibilityUtil.findOnceByTextRegex(item, regex) + //过滤异常好友 + if (searchResult != null && searchResult.parent.childCount < 3) { + item.refresh() + val imageView = + AccessibilityUtil.findOneByClazz(item, Views.ImageView, root = false) + AccessibilityUtil.performClick(imageView) + break + } + } + if (modifyFriendInfo(friend)) { + if (AccessibilityUtil.findTextAndClick(getRoot(), "添加为联系人")) { + LogUtils.d("准备发送好友邀请: ${friend.name}") + if (!friend.leavingMsg.isNullOrEmpty()) { + AccessibilityUtil.findTextInput(getRoot(), friend.leavingMsg) + } + if (AccessibilityUtil.findTextAndClick(getRoot(), "发送添加邀请", "发送申请")) { + LogUtils.d("发送添加邀请成功: ${friend.name}") + uploadCommandResult(message, ExecCallbackBean.SUCCESS, "", startTime) + return true + } else { + if (AccessibilityUtil.findOnceByText(getRoot(), "发消息", exact = true) != null) { + LogUtils.d("已经添加成功: ${friend.name}") + uploadCommandResult(message, ExecCallbackBean.SUCCESS, "", startTime) + return true + } + LogUtils.e("未找到发送邀请按钮") + uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "未找到发送邀请按钮", startTime) + return false + } + } else { + if (AccessibilityUtil.findOnceByText(getRoot(), "发消息", exact = true) != null) { + LogUtils.e("已经添加联系人,请勿重复添加: ${friend.name}") + uploadCommandResult(message, ExecCallbackBean.ERROR_REPEAT, "已经添加联系人,请勿重复添加 ${friend.name}", startTime) + return false + } else { + LogUtils.e("未找到添加为联系人") + uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "未找到添加为联系人", startTime) + return false + } + } + } else { + LogUtils.e("修改用户信息失败: ${friend.name}") + uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "修改用户信息失败: ${friend.name}", startTime) + return false + } + } else { + LogUtils.e("未搜索到结果: ${friend.name}") + uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "未搜索到结果: ${friend.name}", startTime) + return false + } + } else { + LogUtils.e("未发现搜索按钮") + uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "未发现搜索按钮", startTime) + return false + } + } else { + LogUtils.e("未发现通讯录列表") + uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "未发现通讯录列表", startTime) + return false + } + } else { + uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "未找到查看全部群成员按钮 $groupName", startTime) + return false + } + } else { + uploadCommandResult(message, ExecCallbackBean.ERROR_INTO_ROOM, "进入房间失败 $groupName", startTime) + return false + } + } + /** * 展示群信息 * @see WeworkMessageBean.SHOW_GROUP_INFO @@ -884,7 +1010,7 @@ object WeworkOperationImpl { if (matchSelect != null) { LogUtils.d("找到搜索结果: $select") } else { - LogUtils.e("未搜索到结果") + LogUtils.e("未搜索到结果: $select") } sleep(Constant.POP_WINDOW_INTERVAL) } @@ -902,18 +1028,18 @@ object WeworkOperationImpl { AccessibilityUtil.performClick(sendButton) return true } - LogUtils.e("未发现发送按钮: ") + LogUtils.e("未发现发送按钮") return false } else { - LogUtils.e("未发现确认按钮: ") + LogUtils.e("未发现确认按钮") return false } } else { - LogUtils.e("未发现搜索和多选按钮: ") + LogUtils.e("未发现搜索和多选按钮") return false } } - LogUtils.e("未知错误: ") + LogUtils.e("未知错误") return false } @@ -936,6 +1062,15 @@ object WeworkOperationImpl { return false } + /** + * 检查是否达到当日建群上限 + */ + private fun createGroupLimit(): Boolean { + val hasLimit = + AccessibilityUtil.findOneByText(getRoot(), "新建群聊功能暂时被限制", "未验证企业", timeout = 2000) + return hasLimit != null + } + /** * 修改群名称 */ @@ -1047,8 +1182,8 @@ object WeworkOperationImpl { if (matchSelect != null) { LogUtils.d("找到搜索结果: $select") } else { - LogUtils.e("未搜索到结果") - return false + LogUtils.e("未搜索到结果: $select") + if (Constant.groupStrict) return false } } if (showMessageHistory) { @@ -1059,7 +1194,7 @@ object WeworkOperationImpl { if (confirmButton != null) { AccessibilityUtil.performClick(confirmButton) } else { - LogUtils.e("未发现确认按钮: ") + LogUtils.e("未发现确认按钮") return false } } else { @@ -1138,8 +1273,8 @@ object WeworkOperationImpl { if (matchSelect != null) { LogUtils.d("找到搜索结果: $select") } else { - LogUtils.e("未搜索到结果") - return false + LogUtils.e("未搜索到结果: $select") + if (Constant.groupStrict) return false } } val confirmButton = @@ -1147,7 +1282,7 @@ object WeworkOperationImpl { if (confirmButton != null) { AccessibilityUtil.performClick(confirmButton) } else { - LogUtils.e("未发现移出按钮: ") + LogUtils.e("未发现移出按钮") return false } } else { @@ -1193,10 +1328,10 @@ object WeworkOperationImpl { } sleep(3000) } else { - LogUtils.e("无法进行群公告发布: ") + LogUtils.e("无法进行群公告发布") } } else { - LogUtils.e("无法进行群公告发布和编辑: ") + LogUtils.e("无法进行群公告发布和编辑") return false } } else { @@ -1225,10 +1360,10 @@ object WeworkOperationImpl { if (AccessibilityUtil.findTextAndClick(getRoot(), "确定")) { return true } else { - LogUtils.e("无法进行群备注发布: ") + LogUtils.e("无法进行群备注发布") } } else { - LogUtils.e("无法进行群备注修改: ") + LogUtils.e("无法进行群备注修改") } } else { LogUtils.e("未找到群公告按钮") @@ -1352,6 +1487,141 @@ object WeworkOperationImpl { return false } + /** + * 从通讯录查询好友信息 + */ + private fun getFriendInfo(title: String): Boolean { + goHomeTab("通讯录") + val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) + if (list != null) { + val frontNode = AccessibilityUtil.findFrontNode(list) + val textViewList = AccessibilityUtil.findAllOnceByClazz(frontNode, Views.TextView) + if (textViewList.size >= 2) { + val searchButton: AccessibilityNodeInfo = textViewList[textViewList.size - 2] + val multiButton: AccessibilityNodeInfo = textViewList[textViewList.size - 1] + AccessibilityUtil.performClick(searchButton) + val needTrim = title.contains(Constant.regTrimTitle) + val trimTitle = title.replace(Constant.regTrimTitle, "") + AccessibilityUtil.findTextInput(getRoot(), trimTitle) + sleep(Constant.CHANGE_PAGE_INTERVAL) + //消息页搜索结果列表 + val selectListView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) + val reverseRegexTitle = RegexHelper.reverseRegexTitle(trimTitle) + val regex1 = "^$reverseRegexTitle" + if (needTrim) ".*?" else "(-.*)?(…)?(\\(.*?\\))?$" + val regex2 = ".*?\\($reverseRegexTitle\\)$" + val regex = "($regex1)|($regex2)" + val matchSelect = AccessibilityUtil.findOneByTextRegex( + selectListView, + regex, + timeout = 2000, + root = false + ) + if (selectListView != null && matchSelect != null) { + for (i in 0 until selectListView.childCount) { + val item = selectListView.getChild(i) + val searchResult = AccessibilityUtil.findOnceByTextRegex(item, regex) + //过滤异常好友 + if (searchResult != null && searchResult.parent.childCount < 3) { + item.refresh() + val imageView = + AccessibilityUtil.findOneByClazz(item, Views.ImageView, root = false) + AccessibilityUtil.performClick(imageView) + break + } + } + } + if (matchSelect != null) { + LogUtils.d("找到搜索结果: $title") + return true + } else { + LogUtils.e("未搜索到结果: $title") + } + } + } + return false + } + + /** + * 修改好友信息 + */ + private fun modifyFriendInfo(friend: WeworkMessageBean.Friend): Boolean { + if (AccessibilityUtil.findOneByText(getRoot(), "标签", "电话", "描述", "设置备注和描述", exact = true) != null) { + var markTv = AccessibilityUtil.findOnceByText(getRoot(), "设置备注和描述", exact = true) + if (markTv == null) { + markTv = AccessibilityUtil.findOnceByText(getRoot(), "企业", exact = true) + } + if (markTv == null) { + markTv = AccessibilityUtil.findOnceByText(getRoot(), "描述", exact = true) + } + //设置备注 + if (markTv != null && (friend.markName != null + || friend.markCorp != null || friend.markExtra != null) + ) { + AccessibilityUtil.performClick(markTv) + val etList = + AccessibilityUtil.findAllByClazz(getRoot(), Views.EditText, minSize = 2) + if (etList.size >= 5) { + //微信用户 备注/企业/电话/电话/描述 + if (friend.markName != null) { + AccessibilityUtil.editTextInput(etList[0], friend.markName) + } + if (friend.markCorp != null) { + AccessibilityUtil.editTextInput(etList[1], friend.markCorp) + } + if (friend.markExtra != null) { + AccessibilityUtil.editTextInput(etList[4], friend.markExtra) + } + } else if (etList.size == 2) { + //同企业内部用户 备注/描述 + if (friend.markName != null) { + AccessibilityUtil.editTextInput(etList[0], friend.markName) + } + if (friend.markExtra != null) { + AccessibilityUtil.editTextInput(etList[1], friend.markExtra) + } + } else if (etList.size == 3) { + //外部企业用户 备注/电话/描述 + if (friend.markName != null) { + AccessibilityUtil.editTextInput(etList[0], friend.markName) + } + if (friend.markExtra != null) { + AccessibilityUtil.editTextInput(etList[2], friend.markExtra) + } + } + AccessibilityUtil.findTextAndClick(getRoot(), "保存") + } + //设置标签 + if (!friend.tagList.isNullOrEmpty()) { + if (AccessibilityUtil.findTextAndClick(getRoot(), "标签")) { + setFriendTags(friend.tagList) + } + } + //添加联系人 + val imageView = + AccessibilityUtil.findOneByClazz(getRoot(), Views.ImageView) + if (imageView != null) { + val textViewList = AccessibilityUtil.findAllOnceByClazz( + imageView.parent, + Views.TextView + ) + val filter = + textViewList.filter { it.text != null && it.text.toString() != "微信" } + if (filter.isNotEmpty()) { + val tvNick = filter[0] + LogUtils.d("好友昵称或备注名: ${tvNick.text}") + } + } + return true + } else { + if (AccessibilityUtil.findOnceByText(getRoot(), "无权查看") != null) { + LogUtils.e("无权查看该用户") + return false + } + LogUtils.e("未找到标签") + return false + } + } + /** * 设置好友标签 */ @@ -1462,7 +1732,6 @@ object WeworkOperationImpl { ) return true } catch (e: Exception) { - e.printStackTrace() LogUtils.e(e) } } diff --git a/app/src/main/java/org/yameida/worktool/service/WeworkService.kt b/app/src/main/java/org/yameida/worktool/service/WeworkService.kt index a7abb9e..37ff6a2 100644 --- a/app/src/main/java/org/yameida/worktool/service/WeworkService.kt +++ b/app/src/main/java/org/yameida/worktool/service/WeworkService.kt @@ -86,8 +86,10 @@ class WeworkService : AccessibilityService() { Log.e(TAG, "链接建立") val robotId = SPUtils.getInstance().getString(Constant.LISTEN_CHANNEL_ID, "") val appVersion = SPUtils.getInstance().getString("appVersion", "") - val workVersion= SPUtils.getInstance().getString("workVersion", "") - log("链接建立: $robotId appVersion: $appVersion workVersion: $workVersion") + val workVersion = SPUtils.getInstance().getString("workVersion", "") + val deviceRooted = SPUtils.getInstance().getBoolean("deviceRooted", false) + val hook = SPUtils.getInstance().getBoolean("hook", false) + log("链接建立: $robotId appVersion: $appVersion workVersion: $workVersion deviceRooted: $deviceRooted hook: $hook") LogUtils.i("设置自动跳转企业微信") sendBroadcast(true) } diff --git a/app/src/main/java/org/yameida/worktool/utils/envcheck/CheckHook.java b/app/src/main/java/org/yameida/worktool/utils/envcheck/CheckHook.java new file mode 100644 index 0000000..ac1f2fa --- /dev/null +++ b/app/src/main/java/org/yameida/worktool/utils/envcheck/CheckHook.java @@ -0,0 +1,110 @@ +package org.yameida.worktool.utils.envcheck; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.util.Log; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class CheckHook { + + public static boolean isHook(Context context) { + return isHookByPackageName(context) || isHookByStack(context) || isHookByJar(); + } + + /** + * 包名检测 + * + * @param context + * @return + */ + public static boolean isHookByPackageName(Context context) { + boolean isHook = false; + PackageManager packageManager = context.getPackageManager(); + List applicationInfoList = packageManager.getInstalledApplications(PackageManager.GET_META_DATA); + if (null == applicationInfoList) { + return isHook; + } + for (ApplicationInfo applicationInfo : applicationInfoList) { + if (applicationInfo.packageName.equals("de.robv.android.xposed.installer")) { + Log.wtf("HookDetection", "Xposed found on the system."); + isHook = true; + } + if (applicationInfo.packageName.equals("com.saurik.substrate")) { + isHook = true; + Log.wtf("HookDetection", "Substrate found on the system."); + } + } + return isHook; + } + + public static boolean isHookByStack(Context context) { + boolean isHook = false; + try { + throw new Exception("blah"); + } catch (Exception e) { + int zygoteInitCallCount = 0; + for (StackTraceElement stackTraceElement : e.getStackTrace()) { + if (stackTraceElement.getClassName().equals("com.android.internal.os.ZygoteInit")) { + zygoteInitCallCount++; + if (zygoteInitCallCount == 2) { + Log.wtf("HookDetection", "Substrate is active on the device."); + isHook = true; + } + } + if (stackTraceElement.getClassName().equals("com.saurik.substrate.MS$2") && + stackTraceElement.getMethodName().equals("invoked")) { + Log.wtf("HookDetection", "A method on the stack trace has been hooked using Substrate."); + isHook = true; + } + if (stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge") && + stackTraceElement.getMethodName().equals("main")) { + Log.wtf("HookDetection", "Xposed is active on the device."); + isHook = true; + } + if (stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge") && + stackTraceElement.getMethodName().equals("handleHookedMethod")) { + Log.wtf("HookDetection", "A method on the stack trace has been hooked using Xposed."); + isHook = true; + } + + } + } + return isHook; + } + + public static boolean isHookByJar() { + boolean isHook = false; + try { + Set libraries = new HashSet(); + String mapsFilename = "/proc/" + android.os.Process.myPid() + "/maps"; + BufferedReader reader = new BufferedReader(new FileReader(mapsFilename)); + String line; + while ((line = reader.readLine()) != null) { + if (line.endsWith(".so") || line.endsWith(".jar")) { + int n = line.lastIndexOf(" "); + libraries.add(line.substring(n + 1)); + } + } + for (String library : libraries) { + if (library.contains("com.saurik.substrate")) { + Log.wtf("HookDetection", "Substrate shared object found: " + library); + isHook = true; + } + if (library.contains("XposedBridge.jar")) { + Log.wtf("HookDetection", "Xposed JAR found: " + library); + isHook = true; + } + } + reader.close(); + } catch (Exception e) { + Log.wtf("HookDetection", e.toString()); + } + return isHook; + } +} \ No newline at end of file diff --git a/app/src/main/java/org/yameida/worktool/utils/envcheck/CheckRoot.java b/app/src/main/java/org/yameida/worktool/utils/envcheck/CheckRoot.java new file mode 100644 index 0000000..af03372 --- /dev/null +++ b/app/src/main/java/org/yameida/worktool/utils/envcheck/CheckRoot.java @@ -0,0 +1,221 @@ +package org.yameida.worktool.utils.envcheck; + +import android.util.Log; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.ArrayList; + +public class CheckRoot { + private static String LOG_TAG = CheckRoot.class.getName(); + + public static boolean isDeviceRooted() { + if (checkDeviceDebuggable()) { + return true; + } + if (checkSuperuserApk()) { + return true; + } + if (checkRootPathSU()) { + return true; + } + if (checkRootWhichSU()) { + return true; + } + + if (checkAccessRootData()) { + return true; + } + return false; + } + + public static boolean checkDeviceDebuggable() { + String buildTags = android.os.Build.TAGS; + if (buildTags != null && buildTags.contains("test-keys")) { + Log.i(LOG_TAG, "buildTags=" + buildTags); + return true; + } + return false; + } + + public static boolean checkSuperuserApk() { + try { + File file = new File("/system/app/Superuser.apk"); + if (file.exists()) { + Log.i(LOG_TAG, "/system/app/Superuser.apk exist"); + return true; + } + } catch (Exception e) { + } + return false; + } + + public static boolean checkRootPathSU() { + File f = null; + final String kSuSearchPaths[] = {"/system/bin/", "/system/xbin/", "/system/sbin/", "/sbin/", "/vendor/bin/"}; + try { + for (int i = 0; i < kSuSearchPaths.length; i++) { + f = new File(kSuSearchPaths[i] + "su"); + if (f != null && f.exists()) { + Log.i(LOG_TAG, "find su in : " + kSuSearchPaths[i]); + return true; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public static boolean checkRootWhichSU() { + String[] strCmd = new String[]{"/system/xbin/which", "su"}; + ArrayList execResult = executeCommand(strCmd); + if (execResult != null) { + Log.i(LOG_TAG, "execResult=" + execResult.toString()); + return true; + } else { + Log.i(LOG_TAG, "execResult=null"); + return false; + } + } + + public static ArrayList executeCommand(String[] shellCmd) { + String line = null; + ArrayList fullResponse = new ArrayList(); + Process localProcess = null; + try { + Log.i(LOG_TAG, "to shell exec which for find su :"); + localProcess = Runtime.getRuntime().exec(shellCmd); + } catch (Exception e) { + return null; + } + BufferedWriter out = new BufferedWriter(new OutputStreamWriter(localProcess.getOutputStream())); + BufferedReader in = new BufferedReader(new InputStreamReader(localProcess.getInputStream())); + try { + while ((line = in.readLine()) != null) { + Log.i(LOG_TAG, "–> Line received: " + line); + fullResponse.add(line); + } + } catch (Exception e) { + e.printStackTrace(); + } + Log.i(LOG_TAG, "–> Full response was: " + fullResponse); + return fullResponse; + } + + public static synchronized boolean checkGetRootAuth() { + Process process = null; + DataOutputStream os = null; + try { + Log.i(LOG_TAG, "to exec su"); + process = Runtime.getRuntime().exec("su"); + os = new DataOutputStream(process.getOutputStream()); + os.writeBytes("exit\n"); + os.flush(); + int exitValue = process.waitFor(); + Log.i(LOG_TAG, "exitValue=" + exitValue); + if (exitValue == 0) { + return true; + } else { + return false; + } + } catch (Exception e) { + Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + + e.getMessage()); + return false; + } finally { + try { + if (os != null) { + os.close(); + } + process.destroy(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public static synchronized boolean checkBusybox() { + try { + Log.i(LOG_TAG, "to exec busybox df"); + String[] strCmd = new String[]{"busybox", "df"}; + ArrayList execResult = executeCommand(strCmd); + if (execResult != null) { + Log.i(LOG_TAG, "execResult=" + execResult.toString()); + return true; + } else { + Log.i(LOG_TAG, "execResult=null"); + return false; + } + } catch (Exception e) { + Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + + e.getMessage()); + return false; + } + } + + public static synchronized boolean checkAccessRootData() { + try { + Log.i(LOG_TAG, "to write /data"); + String fileContent = "test_ok"; + Boolean writeFlag = writeFile("/data/su_test", fileContent); + if (writeFlag) { + Log.i(LOG_TAG, "write ok"); + } else { + Log.i(LOG_TAG, "write failed"); + } + + Log.i(LOG_TAG, "to read /data"); + String strRead = readFile("/data/su_test"); + Log.i(LOG_TAG, "strRead=" + strRead); + if (fileContent.equals(strRead)) { + return true; + } else { + return false; + } + } catch (Exception e) { + Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + + e.getMessage()); + return false; + } + } + + public static Boolean writeFile(String fileName, String message) { + try { + FileOutputStream fout = new FileOutputStream(fileName); + byte[] bytes = message.getBytes(); + fout.write(bytes); + fout.close(); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + public static String readFile(String fileName) { + File file = new File(fileName); + try { + FileInputStream fis = new FileInputStream(file); + byte[] bytes = new byte[1024]; + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int len; + while ((len = fis.read(bytes)) > 0) { + bos.write(bytes, 0, len); + } + String result = new String(bos.toByteArray()); + Log.i(LOG_TAG, result); + return result; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} \ No newline at end of file