瀏覽代碼

bug修复

yangyang 8 月之前
父節點
當前提交
d8697844a4

+ 4 - 3
app/build.gradle

@@ -9,8 +9,8 @@ android {
         applicationId "com.rk.worktool"
         minSdkVersion 24
         targetSdkVersion 30
-        versionCode 20250306
-        versionName "2.9.0"
+        versionCode 20250324
+        versionName "2.9.2"
     }
 
     buildTypes {
@@ -63,7 +63,8 @@ dependencies {
     implementation project(':xupdate-lib')
     implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
     //友盟统计SDK
-    implementation 'com.umeng.umsdk:common:9.4.7'// 必选
+    implementation 'com.umeng.umsdk:common:9.4.2'// 必选
     implementation 'com.umeng.umsdk:asms:1.4.1'// 必选
     implementation 'com.umeng.umsdk:apm:1.5.2' // 错误分析升级为独立SDK,看crash数据请一定集成,可选
+    implementation 'com.umeng.umsdk:share-board:7.1.6'//分享面板功能,可选
 }

+ 2 - 1
app/src/main/AndroidManifest.xml

@@ -113,7 +113,8 @@
             android:name=".utils.GenericFileProvider"
             android:authorities="org.yameida.worktool.fileprovider"
             android:exported="false"
-            android:grantUriPermissions="true">
+            android:grantUriPermissions="true"
+            tools:replace="android:authorities">
             <meta-data
                 android:name="android.support.FILE_PROVIDER_PATHS"
                 android:resource="@xml/provider_paths" />

+ 2 - 1
app/src/main/java/org/yameida/worktool/Constant.kt

@@ -45,6 +45,7 @@ object Constant {
     const val PACKAGE_NAMES = "com.tencent.wework"
     const val WEWORK_NOTIFY = "wework_notify"
     const val BASE_LONG_INTERVAL = 5000L
+    const val BASE_GENERAL_INTERVAL = 1500L
     const val BASE_CHANGE_PAGE_INTERVAL = 1000L
     const val BASE_POP_WINDOW_INTERVAL = 500L
     const val updateBaseUrl = "https://flowbb.top:6001/sp/phone/download"
@@ -52,7 +53,7 @@ object Constant {
     var CHANGE_PAGE_INTERVAL = BASE_CHANGE_PAGE_INTERVAL
     var POP_WINDOW_INTERVAL = BASE_POP_WINDOW_INTERVAL
 
-    //    private const val DEFAULT_HOST = "wss://assistant.flowbb.top/webSocket/"
+//        private const val DEFAULT_HOST = "wss://assistant.flowbb.top/webSocket/"
     private const val DEFAULT_HOST = "wss://www.mooger8.com/webSocket/"
     var version = Int.MAX_VALUE
     var myName = ""

+ 35 - 10
app/src/main/java/org/yameida/worktool/activity/ListenActivity.kt

@@ -1,7 +1,12 @@
 package org.yameida.worktool.activity
 
 import android.app.ActivityManager
-import android.content.*
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.ServiceConnection
 import android.content.pm.PackageManager
 import android.os.Bundle
 import android.os.Handler
@@ -15,17 +20,38 @@ import android.widget.CompoundButton
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.content.ContextCompat
 import com.blankj.utilcode.constant.PermissionConstants
-import com.blankj.utilcode.util.*
+import com.blankj.utilcode.util.AppUtils
+import com.blankj.utilcode.util.DeviceUtils
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.PermissionUtils
+import com.blankj.utilcode.util.SPUtils
+import com.blankj.utilcode.util.ToastUtils
+import com.blankj.utilcode.util.Utils
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.qmuiteam.qmui.widget.dialog.QMUIDialog
-import kotlinx.android.synthetic.main.activity_listen.*
+import kotlinx.android.synthetic.main.activity_listen.bt_save
+import kotlinx.android.synthetic.main.activity_listen.et_channel
+import kotlinx.android.synthetic.main.activity_listen.iv_settings
+import kotlinx.android.synthetic.main.activity_listen.sw_accessibility
+import kotlinx.android.synthetic.main.activity_listen.sw_overlay
+import kotlinx.android.synthetic.main.activity_listen.tv_host
+import kotlinx.android.synthetic.main.activity_listen.tv_version
+import kotlinx.android.synthetic.main.activity_listen.tv_work_version
 import org.yameida.worktool.Constant
 import org.yameida.worktool.R
 import org.yameida.worktool.model.WeworkMessageBean
 import org.yameida.worktool.service.PlayNotifyService
 import org.yameida.worktool.service.WeworkController
 import org.yameida.worktool.service.fastStartActivity
-import org.yameida.worktool.utils.*
+import org.yameida.worktool.utils.AccessibilityUtil
+import org.yameida.worktool.utils.CacheUtil
+import org.yameida.worktool.utils.FloatWindowHelper
+import org.yameida.worktool.utils.FlowPermissionHelper
+import org.yameida.worktool.utils.HostTestHelper
+import org.yameida.worktool.utils.HttpUtil
+import org.yameida.worktool.utils.PermissionHelper
+import org.yameida.worktool.utils.PermissionPageManagement
+import org.yameida.worktool.utils.Views
 import org.yameida.worktool.utils.capture.MediaProjectionHolder
 import org.yameida.worktool.utils.envcheck.CheckHook
 import org.yameida.worktool.utils.envcheck.CheckRoot
@@ -94,13 +120,12 @@ class ListenActivity : AppCompatActivity() {
             Constant.robotId = channel
             ToastUtils.showLong("保存成功")
             HttpUtil.upgrade(this)
-//            sendBroadcast(Intent(Constant.WEWORK_NOTIFY).apply {
-//                putExtra("type", "modify_channel")
-//            })
-//            HttpUtil.getMyConfig(toast = false)
-//            MobclickAgent.onProfileSignIn(channel)
+//            bt_save.postDelayed({
+//                IWWAPIUtil.sendLink("wwc1f6c9a9876411a6","1000006","https://assistant.flowbb.top/image/f22e830546cff2fdb0eeadfadd58e2bcc54dcf3d3e4088-aw7ly1_fw1200.png",
+//                    "https://wx7546b6c93955bad2-a.wxcp.qidian.com/sog/EXXfC","1","1")
+//            }, 3000)
+//            IWWAPIUtil.init(this)
 
-//            test()
         }
         tv_host.text = Constant.host
         tv_host.setOnClickListener {

+ 4 - 0
app/src/main/java/org/yameida/worktool/model/WeworkMessageBean.java

@@ -104,6 +104,8 @@ public class WeworkMessageBean {
     public static final int SCAN_QR_CODE = 233;
     public static final int DELETE_CONTACT = 234;
 
+    public static final int SEND_GROUP_CHAT = 235;
+
     public static final int ROBOT_LOG = 301;
     public static final int ROBOT_ERROR_LOG = 302;
     public static final int ROBOT_CONTROLLER_TEST = 303;
@@ -311,6 +313,8 @@ public class WeworkMessageBean {
     public Integer weCom;
     public String weworkSchema;
 
+    public boolean isRelay;
+
     public WeworkMessageBean() {
     }
 

+ 64 - 4
app/src/main/java/org/yameida/worktool/service/MyLooper.kt

@@ -16,7 +16,6 @@ import org.yameida.worktool.model.WeworkMessageListBean
 import org.yameida.worktool.utils.FloatWindowHelper
 import org.yameida.worktool.utils.StringFeatureUtil
 import java.nio.charset.StandardCharsets
-import java.util.*
 import kotlin.concurrent.thread
 
 object MyLooper {
@@ -128,7 +127,8 @@ object MyLooper {
             val list =
                 if (Constant.duplicationFilter) LinkedHashSet(messageList.list).toList() else messageList.list
             //去重处理 丢弃之前的重复指令 丢弃之前的获取新消息指令
-            for (message in list) {
+            for (i in list.indices){
+                val message= list[i]
                 if (message.type == WeworkMessageBean.ROBOT_QUEUE_CLEAR) {
                     getInstance().removeCallbacksAndMessages(null)
                     LogUtils.i("清空全部待执行指令")
@@ -140,14 +140,23 @@ object MyLooper {
                         "加入指令到执行队列",
                         if (message.fileBase64.isNullOrEmpty()) GsonUtils.toJson(message) else message.type
                     )
-                    val messageWhat = StringFeatureUtil.generateFeatureValue(text)
+                    var messageWhat = if (list.size > 1) {
+                        StringFeatureUtil.generateFeatureValue(GsonUtils.toJson(message))
+                    } else {
+                        StringFeatureUtil.generateFeatureValue(text)
+                    }
                     if (Constant.duplicationFilter) {
                         getInstance().removeMessages(messageWhat)
                     }
+                    var id = if (list.size > 1) {
+                        messageList.messageId + "?" + i
+                    } else {
+                        messageList.messageId
+                    }
                     getInstance().sendMessage(Message.obtain().apply {
                         what = messageWhat
                         obj = message.apply {
-                            messageId = messageList.messageId
+                            messageId = id
                             action = messageList.action
                             meta = messageList.meta
                             apiSend = messageList.apiSend
@@ -189,146 +198,197 @@ object MyLooper {
             WeworkMessageBean.TYPE_CONSOLE_TOAST -> {
                 WeworkController.consoleToast(message as ExecCallbackBean)
             }
+
             WeworkMessageBean.STOP_AND_GO_HOME -> {
                 WeworkController.stopAndGoHome()
             }
+
             WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE -> {
                 WeworkController.loopReceiveNewMessage()
             }
+
             WeworkMessageBean.SEND_MESSAGE -> {
                 WeworkController.sendMessage(message)
             }
+
             WeworkMessageBean.REPLY_MESSAGE -> {
                 WeworkController.replyMessage(message)
             }
+
             WeworkMessageBean.RELAY_MESSAGE -> {
                 WeworkController.relayMessage(message)
             }
+
             WeworkMessageBean.INIT_GROUP -> {
                 WeworkController.initGroup(message)
             }
+
             WeworkMessageBean.UPDATE_GROUP -> {
                 WeworkController.updateGroup(message)
             }
+
             WeworkMessageBean.PUSH_MICRO_DISK_IMAGE -> {
                 WeworkController.pushMicroDiskImage(message)
             }
+
             WeworkMessageBean.PUSH_MICRO_DISK_FILE -> {
                 WeworkController.pushMicroDiskFile(message)
             }
+
             WeworkMessageBean.PUSH_MICROPROGRAM -> {
                 WeworkController.pushMicroprogram(message)
             }
+
             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.PUSH_LINK -> {
                 WeworkController.pushLink(message)
             }
+
             WeworkMessageBean.RECALL_MESSAGE -> {
                 WeworkController.recallMessage(message)
             }
+
             WeworkMessageBean.RELAY_MULTI_MESSAGE -> {
                 WeworkController.relayMultiMessage(message)
             }
+
             WeworkMessageBean.RELAY_MERGE_MESSAGE -> {
                 WeworkController.relayMergeMessage(message)
             }
+
             WeworkMessageBean.SEND_MULTI_MESSAGE -> {
                 WeworkController.sendMultiMessage(message)
             }
+
             WeworkMessageBean.SEND_MERGE_MESSAGE -> {
                 WeworkController.sendMergeMessage(message)
             }
+
             WeworkMessageBean.SCAN_QR_CODE -> {
                 WeworkController.scanQrCode(message)
             }
+
             WeworkMessageBean.DELETE_CONTACT -> {
                 WeworkController.deleteContact(message)
             }
+
             WeworkMessageBean.DISMISS_GROUP -> {
                 WeworkController.dismissGroup(message)
             }
+
             WeworkMessageBean.ADD_FRIEND_BY_GROUP -> {
                 WeworkController.addFriendByGroup(message)
             }
+
             WeworkMessageBean.MODIFY_GROUP_MEMBER_INFO -> {
                 WeworkController.modifyGroupMemberInfo(message)
             }
+
             WeworkMessageBean.ADD_NEED_DEAL -> {
                 WeworkController.addNeedDeal(message)
             }
+
             WeworkMessageBean.CLOCK_IN -> {
                 WeworkController.clockIn(message)
             }
+
             WeworkMessageBean.SWITCH_CORP -> {
                 WeworkController.switchCorp(message)
             }
+
             WeworkMessageBean.SHOW_GROUP_INFO -> {
                 WeworkController.showGroupInfo(message)
             }
+
             WeworkMessageBean.GET_GROUP_INFO -> {
                 WeworkController.getGroupInfo(message)
             }
+
             WeworkMessageBean.GET_FRIEND_INFO -> {
                 WeworkController.getFriendInfo(message)
             }
+
             WeworkMessageBean.GET_MY_INFO -> {
                 WeworkController.getMyInfo(message)
             }
+
             WeworkMessageBean.GET_RECENT_LIST -> {
                 WeworkController.getRecentList(message)
             }
+
             WeworkMessageBean.GET_ALL_FRIEND_INFO -> {
                 WeworkController.getAllFriendInfo(message)
             }
+
             WeworkMessageBean.GET_ALL_GROUP_INFO -> {
                 WeworkController.getAllGroupInfo(message)
             }
+
             WeworkMessageBean.GET_LOCAL_FILE -> {
                 WeworkController.getLocalFile(message)
             }
+
             WeworkMessageBean.GET_CORP_LIST -> {
                 WeworkController.getCorpList(message)
             }
+
             WeworkMessageBean.ROBOT_CONTROLLER_TEST -> {
                 WeworkController.test(message)
             }
+
             WeworkMessageBean.TRANSFER_GROUP -> {
                 WeworkController.transferGroup(message)
             }
+
             WeworkMessageBean.ADMINISTRATORS -> {
                 WeworkController.administrators(message)
             }
+
             WeworkMessageBean.REMOVE_ADMINISTRATORS -> {
                 WeworkController.removeAdministrators(message)
             }
+
             WeworkMessageBean.ADD_HARASS_RULE -> {
                 WeworkController.setHarassRules(message)
             }
+
             WeworkMessageBean.DELETE_HARASS_RULE -> {
                 WeworkController.deleteHarassRules(message)
             }
+
             WeworkMessageBean.CANCEL_HARASS_RULE -> {
                 WeworkController.cancelHarassRules(message)
             }
+
             WeworkMessageBean.APPLY_HARASS_RULE -> {
                 WeworkController.applyHarassRules(message)
             }
+
             WeworkMessageBean.SWITCH_WE_COM -> {
                 WeworkController.switchWeCom(message)
             }
+
             WeworkMessageBean.INIT -> {
                 WeworkController.init(message)
             }
+
+            WeworkMessageBean.SEND_GROUP_CHAT -> {
+                WeworkController.sendGroupChat(message)
+            }
         }
     }
 }

+ 9 - 0
app/src/main/java/org/yameida/worktool/service/WeworkController.kt

@@ -762,4 +762,13 @@ object WeworkController {
     fun init(message: WeworkMessageBean):Boolean{
         return WeworkOperationImpl.init(message)
     }
+
+    /**
+     * 发送点对点群发
+     * @see WeworkMessageBean.GET_CORP_LIST
+     */
+    @RequestMapping
+    fun sendGroupChat(message: WeworkMessageBean):Boolean{
+        return WeworkOperationImpl.sendGroupChat(message)
+    }
 }

+ 34 - 7
app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt

@@ -5,7 +5,13 @@ import android.os.Message
 import android.util.Log
 import android.view.accessibility.AccessibilityNodeInfo
 import androidx.core.text.isDigitsOnly
-import com.blankj.utilcode.util.*
+import com.blankj.utilcode.util.BarUtils
+import com.blankj.utilcode.util.FileUtils
+import com.blankj.utilcode.util.ImageUtils
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.SPUtils
+import com.blankj.utilcode.util.ScreenUtils
+import com.blankj.utilcode.util.Utils
 import com.hjq.toast.ToastUtils
 import org.yameida.worktool.Constant
 import org.yameida.worktool.MyApplication
@@ -13,11 +19,14 @@ import org.yameida.worktool.activity.GetScreenShotActivity
 import org.yameida.worktool.model.WeworkMessageBean
 import org.yameida.worktool.observer.MultiFileObserver
 import org.yameida.worktool.service.WeworkController.mainLoopRunning
-import org.yameida.worktool.utils.*
+import org.yameida.worktool.utils.AccessibilityExtraUtil
+import org.yameida.worktool.utils.AccessibilityUtil
+import org.yameida.worktool.utils.Views
+import org.yameida.worktool.utils.WeworkRoomUtil
+import org.yameida.worktool.utils.WeworkTextUtil
 import java.io.File
 import java.text.SimpleDateFormat
-import java.util.*
-import kotlin.collections.LinkedHashSet
+import java.util.Date
 
 /**
  * 获取数据类型 201 202 主循环
@@ -129,6 +138,8 @@ object WeworkLoopImpl {
                         goHome()
                         return false
                     }
+                    AccessibilityUtil.scrollToTop(WeworkController.weworkService, getRoot())
+                    sleep(Constant.POP_WINDOW_INTERVAL)
                     val addButton = AccessibilityUtil.findOneByText(getRoot(), "添加")
                     var parent = addButton
                     var son = addButton
@@ -284,18 +295,23 @@ object WeworkLoopImpl {
                     WeworkMessageBean.TEXT_TYPE_IMAGE -> {
                         "[图片]"
                     }
+
                     WeworkMessageBean.TEXT_TYPE_CHANNELS_VIDEO -> {
                         "[视频]"
                     }
+
                     WeworkMessageBean.TEXT_TYPE_LINK -> {
                         "[链接]"
                     }
+
                     WeworkMessageBean.TEXT_TYPE_FILE -> {
                         "[文件]"
                     }
+
                     WeworkMessageBean.TEXT_TYPE_MICROPROGRAM -> {
                         "[小程序]"
                     }
+
                     else -> {
                         lastMessage.itemMessageList.lastOrNull()?.text
                     }
@@ -357,6 +373,7 @@ object WeworkLoopImpl {
                             )
                         )
                     }
+
                     lastSyncMessage.contains("开启了联系人验证") -> {
                         WeworkController.weworkService.webSocketManager.send(
                             WeworkMessageBean(
@@ -369,6 +386,7 @@ object WeworkLoopImpl {
                             )
                         )
                     }
+
                     lastSyncMessage.contains("因账号违规") -> {
                         WeworkController.weworkService.webSocketManager.send(
                             WeworkMessageBean(
@@ -381,6 +399,7 @@ object WeworkLoopImpl {
                             )
                         )
                     }
+
                     else -> {
                         WeworkController.weworkService.webSocketManager.send(
                             WeworkMessageBean(
@@ -423,6 +442,7 @@ object WeworkLoopImpl {
                                     )
                                 }
                             }
+
                             2 -> {
                                 val startTime = System.currentTimeMillis()
                                 var currentTime = startTime
@@ -432,6 +452,7 @@ object WeworkLoopImpl {
                                 }
                                 return getChatMessageList(needInfer = false, titleList = titleList)
                             }
+
                             else -> return true
                         }
                     }
@@ -563,7 +584,12 @@ object WeworkLoopImpl {
                     AccessibilityUtil.performClick(textNode)
                 }
                 textNode =
-                    AccessibilityUtil.findOneByText(getRoot(), "发消息", "添加请求已过期,添加失败", exact = true)
+                    AccessibilityUtil.findOneByText(
+                        getRoot(),
+                        "发消息",
+                        "添加请求已过期,添加失败",
+                        exact = true
+                    )
                 if (textNode?.text?.toString() == "添加请求已过期,添加失败") {
                     LogUtils.d("添加好友失败")
                 } else {
@@ -740,9 +766,10 @@ object WeworkLoopImpl {
                         && spotNode.text != null
                         && spotNode.text.toString().replace("+", "").isDigitsOnly()
                     ) {
-                        if (item.getChild(2).getChild(0).text.toString() != "客户朋友圈") {
-                            spotNodeList.add(spotNode)
+                        if (item.getChild(2).getChild(0).text != null && (item.getChild(2).getChild(0).text.toString() == "客户朋友圈" || item.getChild(2).getChild(0).text.toString() == "学员朋友圈")) {
+                            continue
                         }
+                        spotNodeList.add(spotNode)
                     }
                 }
             }

+ 402 - 36
app/src/main/java/org/yameida/worktool/service/WeworkOperationImpl.kt

@@ -16,7 +16,6 @@ import org.yameida.worktool.utils.*
 import java.io.File
 import java.text.SimpleDateFormat
 import java.util.*
-import kotlin.collections.LinkedHashSet
 
 
 /**
@@ -261,7 +260,8 @@ object WeworkOperationImpl {
                         )
                     ) {
                         LogUtils.d("开始转发")
-                        val relaySelectResult = relaySelectTarget(nameList, extraText)
+                        val relaySelectResult =
+                            newRelaySelectTarget(nameList, extraText, isRelay = true)
                         if (relaySelectResult.result) {
                             AccessibilityExtraUtil.loadingPage(
                                 "CommonSelectActivity",
@@ -692,7 +692,12 @@ object WeworkOperationImpl {
             if (groupManagerTv != null) {
                 AccessibilityUtil.performClick(groupManagerTv)
                 val dismissTv =
-                    AccessibilityUtil.findOneByText(getRoot(), "解散群聊", exact = true, timeout = 2000)
+                    AccessibilityUtil.findOneByText(
+                        getRoot(),
+                        "解散群聊",
+                        exact = true,
+                        timeout = 2000
+                    )
                 AccessibilityUtil.performClick(dismissTv)
                 if (dismissTv != null) {
                     val confirmTv = AccessibilityUtil.findOneByText(
@@ -841,7 +846,10 @@ object WeworkOperationImpl {
                         AccessibilityUtil.performClick(shareToWorkButton)
                         sleep(Constant.POP_WINDOW_INTERVAL)
                         shareToWorkButton =
-                            AccessibilityUtil.findOnceByText(getRoot(true, share = true), "发送给同事")
+                            AccessibilityUtil.findOnceByText(
+                                getRoot(true, share = true),
+                                "发送给同事"
+                            )
                         LogUtils.v(
                             "尝试发送给同事",
                             shareToWorkButton == null,
@@ -1202,7 +1210,8 @@ object WeworkOperationImpl {
                 description
             )
         ) {
-            val relaySelectResult = relaySelectTarget(titleList, extraText)
+            val relaySelectResult =
+                newRelaySelectTarget(titleList, extraText)
             if (relaySelectResult.result) {
                 AccessibilityExtraUtil.loadingPage(
                     "CommonSelectActivity",
@@ -1211,6 +1220,18 @@ object WeworkOperationImpl {
                     "MessageListActivity"
                 )
                 AccessibilityUtil.waitForPageMissing("CommonSelectActivity")
+                if (message.isRelay) {
+                    val tvFlag = AccessibilityUtil.findOneByText(
+                        getRoot(true, share = true),
+                        "留在企业微信"
+                    )
+                    if (tvFlag != null) {
+                        AccessibilityUtil.performClick(tvFlag)
+                    }
+                    message.weCom = 2
+                    switchWeCom(message)
+                }
+                sleep(Constant.BASE_CHANGE_PAGE_INTERVAL)
                 uploadCommandResult(
                     message,
                     ExecCallbackBean.SUCCESS,
@@ -1454,7 +1475,10 @@ object WeworkOperationImpl {
                         AccessibilityUtil.performClick(shareToWorkButton)
                         sleep(Constant.POP_WINDOW_INTERVAL)
                         shareToWorkButton =
-                            AccessibilityUtil.findOnceByText(getRoot(true, share = true), "发送给同事")
+                            AccessibilityUtil.findOnceByText(
+                                getRoot(true, share = true),
+                                "发送给同事"
+                            )
                         LogUtils.v(
                             "尝试发送给同事",
                             shareToWorkButton == null,
@@ -1825,7 +1849,8 @@ object WeworkOperationImpl {
                 receivedContent
             )
         ) {
-            val relaySelectResult = relaySelectTarget(titleList, extraText)
+            val relaySelectResult =
+                newRelaySelectTarget(titleList, extraText)
             if (relaySelectResult.result) {
                 AccessibilityExtraUtil.loadingPage(
                     "CommonSelectActivity",
@@ -1842,6 +1867,10 @@ object WeworkOperationImpl {
                     relaySelectResult.successList,
                     relaySelectResult.failList
                 )
+                if (message.isRelay) {
+                    message.weCom = 2
+                    switchWeCom(message)
+                }
                 goHome()
                 return true
             } else {
@@ -2098,21 +2127,27 @@ object WeworkOperationImpl {
                 WeworkMessageBean.SEND_MESSAGE -> {
                     WeworkController.sendMessage(weworkMessage)
                 }
+
                 WeworkMessageBean.PUSH_MICRO_DISK_IMAGE -> {
                     WeworkController.pushMicroDiskImage(weworkMessage)
                 }
+
                 WeworkMessageBean.PUSH_MICRO_DISK_FILE -> {
                     WeworkController.pushMicroDiskFile(weworkMessage)
                 }
+
                 WeworkMessageBean.PUSH_MICROPROGRAM -> {
                     WeworkController.pushMicroprogram(weworkMessage)
                 }
+
                 WeworkMessageBean.PUSH_OFFICE -> {
                     WeworkController.pushOffice(weworkMessage)
                 }
+
                 WeworkMessageBean.PUSH_FILE -> {
                     WeworkController.pushFile(weworkMessage)
                 }
+
                 WeworkMessageBean.PUSH_LINK -> {
                     WeworkController.pushLink(weworkMessage)
                 }
@@ -2144,21 +2179,27 @@ object WeworkOperationImpl {
                     WeworkMessageBean.SEND_MESSAGE -> {
                         WeworkController.sendMessage(weworkMessage)
                     }
+
                     WeworkMessageBean.PUSH_MICRO_DISK_IMAGE -> {
                         WeworkController.pushMicroDiskImage(weworkMessage)
                     }
+
                     WeworkMessageBean.PUSH_MICRO_DISK_FILE -> {
                         WeworkController.pushMicroDiskFile(weworkMessage)
                     }
+
                     WeworkMessageBean.PUSH_MICROPROGRAM -> {
                         WeworkController.pushMicroprogram(weworkMessage)
                     }
+
                     WeworkMessageBean.PUSH_OFFICE -> {
                         WeworkController.pushOffice(weworkMessage)
                     }
+
                     WeworkMessageBean.PUSH_FILE -> {
                         WeworkController.pushFile(weworkMessage)
                     }
+
                     WeworkMessageBean.PUSH_LINK -> {
                         WeworkController.pushLink(weworkMessage)
                     }
@@ -3518,7 +3559,12 @@ object WeworkOperationImpl {
             if (clockInFlag != null) {
                 AccessibilityUtil.findTextAndClick(getRoot(), "上班打卡", "下班打卡")
                 val doneFlag =
-                    AccessibilityUtil.findOneByText(getRoot(), "上班·正常", "之后可打下班卡", "今日打卡已完成")
+                    AccessibilityUtil.findOneByText(
+                        getRoot(),
+                        "上班·正常",
+                        "之后可打下班卡",
+                        "今日打卡已完成"
+                    )
                 if (doneFlag != null) {
                     LogUtils.d("打卡成功")
                     uploadCommandResult(message, ExecCallbackBean.SUCCESS, "", startTime)
@@ -3535,12 +3581,22 @@ object WeworkOperationImpl {
                 }
             } else {
                 LogUtils.e("未找到上下班打卡")
-                uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "未找到上下班打卡", startTime)
+                uploadCommandResult(
+                    message,
+                    ExecCallbackBean.ERROR_BUTTON,
+                    "未找到上下班打卡",
+                    startTime
+                )
                 return false
             }
         } else {
             LogUtils.e("未找到在打卡范围")
-            uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "未找到在打卡范围", startTime)
+            uploadCommandResult(
+                message,
+                ExecCallbackBean.ERROR_BUTTON,
+                "未找到在打卡范围",
+                startTime
+            )
             return false
         }
     }
@@ -3782,7 +3838,12 @@ object WeworkOperationImpl {
                 }
             } else {
                 LogUtils.e("未找到搜索按钮")
-                uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "未找到搜索按钮", startTime)
+                uploadCommandResult(
+                    message,
+                    ExecCallbackBean.ERROR_BUTTON,
+                    "未找到搜索按钮",
+                    startTime
+                )
                 return false
             }
         } else {
@@ -3971,6 +4032,165 @@ object WeworkOperationImpl {
                 getRoot(),
                 "选择联系人",
                 "选择参与人",
+                "选择聊天",
+                exact = true,
+                timeout = timeout
+            ) == null
+        ) {
+            LogUtils.e("未找到选择联系人/选择参与人")
+            error("未找到选择联系人/选择参与人")
+            return selectResult
+        }
+        //聊天消息列表 1ListView 0RecycleView xViewGroup
+        val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
+        if (list != null) {
+            val frontNode = AccessibilityUtil.findFrontNode(list, 2)
+            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(multiButton)
+                AccessibilityUtil.performClick(searchButton)
+                var isSelect = false
+                for (select in LinkedHashSet(selectList)) {
+                    val needTrim = select.contains(Constant.regTrimTitle)
+                    val trimTitle = select.replace(Constant.regTrimTitle, "")
+                    AccessibilityUtil.findTextInput(getRoot(), trimTitle)
+                    sleep(Constant.CHANGE_PAGE_INTERVAL)
+                    val selectListView = AccessibilityUtil.findOneByClazz(
+                        getRoot(),
+                        Views.ListView,
+                        Views.RecyclerView,
+                        Views.ViewGroup,
+                        minChildCount = 2
+                    )
+                    val reverseRegexTitle = RegexHelper.reverseRegexTitle(trimTitle)
+                    val regex1 =
+                        (if (Constant.friendRemarkStrict) "^$reverseRegexTitle" else "^(微信昵称:)|((企业)?邮箱:)?$reverseRegexTitle") +
+                                (if (needTrim) ".*?" else Constant.suffixString)
+                    val regex2 = ".*?\\($reverseRegexTitle\\)$"
+                    val regex = "($regex1)|($regex2)"
+                    val matchSelect = AccessibilityUtil.findOneByTextRegex(
+                        selectListView,
+                        regex,
+                        timeout = Constant.CHANGE_PAGE_INTERVAL * 3,
+                        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?.parent != null && searchResult.parent.childCount < 3) {
+                                item.refresh()
+                                val imageView = AccessibilityUtil.findOneByClazz(
+                                    item,
+                                    Views.ImageView,
+                                    root = false
+                                )
+                                AccessibilityUtil.performClick(imageView)
+                                isSelect = true
+                                break
+                            }
+                        }
+                    }
+                    if (matchSelect != null) {
+                        selectResult.successList.add(select)
+                        LogUtils.d("找到搜索结果: $select")
+                    } else {
+                        selectResult.failList.add(select)
+                        LogUtils.e("未搜索到结果: $select")
+                        error("未搜索到结果: $select")
+                        val noResult = AccessibilityUtil.findOnceByText(
+                            getRoot(),
+                            "无搜索结果",
+                            "暂无搜索结果",
+                            exact = true
+                        ) != null
+                        LogUtils.e("企微: 无搜索结果: $noResult")
+                    }
+                    sleep(Constant.POP_WINDOW_INTERVAL)
+                }
+                if (!isSelect) {
+                    LogUtils.e("未选择接收者")
+                    error("未选择接收者")
+                    return selectResult
+                }
+                val confirmButton =
+                    AccessibilityUtil.findOneByTextRegex(getRoot(), "^确定(\\(\\d+\\))?\$")
+                if (confirmButton != null) {
+                    AccessibilityUtil.performClick(confirmButton)
+                    sleep(Constant.POP_WINDOW_INTERVAL)
+                    if (!needSend) {
+                        selectResult.result = true
+                        return selectResult
+                    }
+                    if (!extraText.isNullOrBlank()) {
+                        LogUtils.d("extraText: $extraText")
+                        AccessibilityUtil.findTextInput(getRoot(), extraText)
+                    }
+                    val sendButton =
+                        AccessibilityUtil.findOneByTextRegex(getRoot(), "^发送(\\(\\d+\\))?\$")
+                    if (sendButton != null) {
+                        AccessibilityUtil.performClick(sendButton)
+                        selectResult.result = true
+                        return selectResult
+                    }
+                    LogUtils.e("未发现发送按钮")
+                    error("未发现发送按钮")
+                    return selectResult
+                } else {
+                    LogUtils.e("未发现确认按钮")
+                    error("未发现确认按钮")
+                    return selectResult
+                }
+            } else {
+                LogUtils.e("未发现搜索和多选按钮")
+                error("未发现搜索和多选按钮")
+                return selectResult
+            }
+        }
+        LogUtils.e("未知错误")
+        error("未知错误")
+        return selectResult
+    }
+
+    /**
+     * 转发消息到目标列表
+     * 支持场景:长按消息转发、微盘图片转发
+     * selectList 昵称或群名列表
+     * extraText 转发是否附加一条文本
+     */
+    private fun newRelaySelectTarget(
+        selectList: List<String>,
+        extraText: String? = null,
+        needSend: Boolean = true,
+        timeout: Long = 5000,
+        isRelay: Boolean = false
+    ): SelectResult {
+        val selectResult = SelectResult()
+        if (!isRelay) {
+            if (SPUtils.getInstance().getInt("num") == 2) {
+                sleep(Constant.POP_WINDOW_INTERVAL)
+                val aList = AccessibilityUtil.findAllByText(
+                    WeworkController.weworkService.rootInActiveWindow,
+                    "企业微信"
+                )
+                if (aList.size == 2) {
+//                if (1 == SPUtils.getInstance().getInt("weCom")) {
+                    AccessibilityUtil.performClick(aList.firstOrNull())
+//                } else {
+//                    AccessibilityUtil.performClick(aList.lastOrNull())
+//                }
+                }
+                sleep(Constant.BASE_CHANGE_PAGE_INTERVAL)
+            }
+        }
+        if (AccessibilityUtil.findOneByText(
+                getRoot(),
+                "选择联系人",
+                "选择参与人",
+                "选择聊天",
                 exact = true,
                 timeout = timeout
             ) == null
@@ -4111,7 +4331,12 @@ object WeworkOperationImpl {
             sleep(Constant.POP_WINDOW_INTERVAL)
             LogUtils.d("进入客户群应用")
             val textView =
-                AccessibilityUtil.findOneByText(getRoot(), "创建一个客户群", "创建一个居民群", "创建一个学员群")
+                AccessibilityUtil.findOneByText(
+                    getRoot(),
+                    "创建一个客户群",
+                    "创建一个居民群",
+                    "创建一个学员群"
+                )
             return AccessibilityUtil.performClick(textView)
         }
         LogUtils.e("未找到客户群应用")
@@ -4142,7 +4367,12 @@ object WeworkOperationImpl {
      */
     private fun createGroupLimit(): Boolean {
         val hasLimit =
-            AccessibilityUtil.findOneByText(getRoot(), "新建群聊功能暂时被限制", "未验证企业", timeout = 2000)
+            AccessibilityUtil.findOneByText(
+                getRoot(),
+                "新建群聊功能暂时被限制",
+                "未验证企业",
+                timeout = 2000
+            )
         if (hasLimit == null) {
             SPUtils.getInstance("limit").put("canCreateGroup", true)
         } else if (SPUtils.getInstance("limit").getBoolean("canCreateGroup", false)) {
@@ -4158,7 +4388,12 @@ object WeworkOperationImpl {
      */
     private fun createGroupError(): Boolean {
         val hasLimit =
-            AccessibilityUtil.findOneByText(getRoot(), "账号异常", "新建客户群功能已被限制使用", timeout = 2000)
+            AccessibilityUtil.findOneByText(
+                getRoot(),
+                "账号异常",
+                "新建客户群功能已被限制使用",
+                timeout = 2000
+            )
         return hasLimit != null
     }
 
@@ -4601,7 +4836,12 @@ object WeworkOperationImpl {
                     while (retry++ < 10) {
                         AccessibilityUtil.performClick(editButton)
                         sleep(Constant.POP_WINDOW_INTERVAL)
-                        if (AccessibilityUtil.findOnceByText(getRoot(), "编辑", exact = true) == null)
+                        if (AccessibilityUtil.findOnceByText(
+                                getRoot(),
+                                "编辑",
+                                exact = true
+                            ) == null
+                        )
                             break
                     }
                 }
@@ -4701,7 +4941,8 @@ object WeworkOperationImpl {
                             timeout = 2000
                         )
                         if (useTv == null) {
-                            val useTemplateTv = AccessibilityUtil.findOneByDesc(getRoot(), "使用该模板")
+                            val useTemplateTv =
+                                AccessibilityUtil.findOneByDesc(getRoot(), "使用该模板")
                             if (useTemplateTv != null) {
                                 AccessibilityUtil.performClick(useTemplateTv)
                             }
@@ -4770,11 +5011,16 @@ object WeworkOperationImpl {
             error("非聊天房间 无法发送消息")
             return false
         }
-        val voiceFlag = AccessibilityUtil.findOnceByText(getRoot(), "按住 说话", "按住说话", exact = true)
+        val voiceFlag =
+            AccessibilityUtil.findOnceByText(getRoot(), "按住 说话", "按住说话", exact = true)
         if (voiceFlag != null) {
             AccessibilityUtil.performClickWithSon(AccessibilityUtil.findFrontNode(voiceFlag))
         }
-        val quitFlag = AccessibilityUtil.findOnceByText(getRoot(), "无法在已退出的群聊中发送消息", exact = true)
+        val quitFlag = AccessibilityUtil.findOnceByText(
+            getRoot(),
+            "无法在已退出的群聊中发送消息",
+            exact = true
+        )
         if (quitFlag != null) {
             LogUtils.e("无法在已退出的群聊中发送消息 title: $title")
             error("无法在已退出的群聊中发送消息 title: $title")
@@ -4829,9 +5075,11 @@ object WeworkOperationImpl {
                         AccessibilityUtil.findOnceByClazz(getRoot(), Views.EditText), "@"
                     )
                 }
-                val atFlag = AccessibilityUtil.findOneByText(getRoot(), "选择提醒的人", exact = true)
+                val atFlag =
+                    AccessibilityUtil.findOneByText(getRoot(), "选择提醒的人", exact = true)
                 if (atFlag != null) {
-                    val searchFlag = AccessibilityUtil.findOneByText(getRoot(), "搜索", exact = true)
+                    val searchFlag =
+                        AccessibilityUtil.findOneByText(getRoot(), "搜索", exact = true)
                     val container =
                         AccessibilityUtil.findBackNode(searchFlag, minChildCount = 2)?.parent
                     if (container != null) {
@@ -5075,10 +5323,16 @@ object WeworkOperationImpl {
      */
     fun setFriendTags(tagList: List<String>): Boolean {
         val tagList = if (tagList.size > 5) tagList.subList(0, 5) else tagList
-        var tvTag = AccessibilityUtil.findAllByText(getRoot(), "个人标签", exact = true).lastOrNull()
+        var tvTag =
+            AccessibilityUtil.findAllByText(getRoot(), "个人标签", exact = true).lastOrNull()
         if (tvTag == null) {
-            AccessibilityUtil.scrollAndFindByText(WeworkController.weworkService, getRoot(), "个人标签")
-            tvTag = AccessibilityUtil.findAllByText(getRoot(), "个人标签", exact = true).lastOrNull()
+            AccessibilityUtil.scrollAndFindByText(
+                WeworkController.weworkService,
+                getRoot(),
+                "个人标签"
+            )
+            tvTag =
+                AccessibilityUtil.findAllByText(getRoot(), "个人标签", exact = true).lastOrNull()
         }
         val oldTagList = arrayListOf<String>()
         val list = AccessibilityUtil.findBackNode(tvTag)
@@ -5134,7 +5388,13 @@ object WeworkOperationImpl {
                 sleep(Constant.POP_WINDOW_INTERVAL)
                 //可能有两次确定 另一次为添加新tag
                 val textNode =
-                    AccessibilityUtil.findOneByText(getRoot(), "确定", "保存", "个人信息", exact = true)
+                    AccessibilityUtil.findOneByText(
+                        getRoot(),
+                        "确定",
+                        "保存",
+                        "个人信息",
+                        exact = true
+                    )
                 if (textNode?.text?.toString() in arrayOf("确定", "保存")) {
                     AccessibilityUtil.performClick(textNode)
                 }
@@ -5261,7 +5521,12 @@ object WeworkOperationImpl {
             if (groupManagerTv != null) {
                 AccessibilityUtil.performClick(groupManagerTv)
                 val dismissTv =
-                    AccessibilityUtil.findOneByText(getRoot(), "转让群主", exact = true, timeout = 2000)
+                    AccessibilityUtil.findOneByText(
+                        getRoot(),
+                        "转让群主",
+                        exact = true,
+                        timeout = 2000
+                    )
                 AccessibilityUtil.performClick(dismissTv)
                 val selectListView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
                 if (selectListView != null) {
@@ -5337,7 +5602,12 @@ object WeworkOperationImpl {
             if (groupManagerTv != null) {
                 AccessibilityUtil.performClick(groupManagerTv)
                 val dismissTv =
-                    AccessibilityUtil.findOneByText(getRoot(), "群管理员", exact = true, timeout = 2000)
+                    AccessibilityUtil.findOneByText(
+                        getRoot(),
+                        "群管理员",
+                        exact = true,
+                        timeout = 2000
+                    )
                 AccessibilityUtil.performClick(dismissTv)
                 sleep(Constant.POP_WINDOW_INTERVAL)
                 val gridView = AccessibilityUtil.findOnceByClazz(getRoot(), Views.GridView)
@@ -5516,7 +5786,12 @@ object WeworkOperationImpl {
             if (groupManagerTv != null) {
                 AccessibilityUtil.performClick(groupManagerTv)
                 val dismissTv =
-                    AccessibilityUtil.findOneByText(getRoot(), "群管理员", exact = true, timeout = 2000)
+                    AccessibilityUtil.findOneByText(
+                        getRoot(),
+                        "群管理员",
+                        exact = true,
+                        timeout = 2000
+                    )
                 AccessibilityUtil.performClick(dismissTv)
                 sleep(Constant.POP_WINDOW_INTERVAL)
                 val gridView = AccessibilityUtil.findOnceByClazz(getRoot(), Views.GridView)
@@ -5674,7 +5949,12 @@ object WeworkOperationImpl {
                 AccessibilityUtil.performClick(textView)
             }
             val tvAddressFlag =
-                AccessibilityUtil.findOneByText(getRoot(), "禁止改群名", exact = true, timeout = 2000)
+                AccessibilityUtil.findOneByText(
+                    getRoot(),
+                    "禁止改群名",
+                    exact = true,
+                    timeout = 2000
+                )
             if (tvAddressFlag != null) {
                 val tvAddress = AccessibilityUtil.findBackNode(tvAddressFlag, minChildCount = 1)
                 val addressDesc =
@@ -6467,7 +6747,7 @@ object WeworkOperationImpl {
             ?.apply {
                 this.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                 startActivity(this)
-                sleep(Constant.POP_WINDOW_INTERVAL)
+                sleep(Constant.BASE_GENERAL_INTERVAL)
                 // 查找并点击第一个应用
                 val rootNode = WeworkController.weworkService.rootInActiveWindow
                 var aa = AccessibilityUtil.findAllByClazz(rootNode, Views.ImageView)
@@ -6477,13 +6757,15 @@ object WeworkOperationImpl {
                     AccessibilityUtil.findAllByClazz(rootNode, Views.ImageView).lastOrNull()!!
                 }
                 AccessibilityUtil.performClick(firstAppNode)
-                uploadCommandResult(
-                    message,
-                    ExecCallbackBean.SUCCESS,
-                    "",
-                    System.currentTimeMillis()
-                )
-                sleep(Constant.POP_WINDOW_INTERVAL)
+                if (!message.isRelay) {
+                    uploadCommandResult(
+                        message,
+                        ExecCallbackBean.SUCCESS,
+                        "",
+                        System.currentTimeMillis()
+                    )
+                    sleep(Constant.POP_WINDOW_INTERVAL)
+                }
                 SPUtils.getInstance().put("weCom", message.weCom)
                 WeworkController.weworkService.webSocketManager.send(
                     WeworkMessageBean(
@@ -6512,4 +6794,88 @@ object WeworkOperationImpl {
         )
         return true
     }
+
+    fun sendGroupChat(message: WeworkMessageBean):Boolean{
+        if (WeworkRoomUtil.intoGroupChat("群发助手") || WeworkRoomUtil.intoGroupChat("群发助手", fastIn = false)) {
+            Constant.lastUseMultiSender = System.currentTimeMillis()
+            val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
+            if (list != null) {
+                var findDayFlag = false
+                while (!findDayFlag) {
+                    val childCount = list.childCount
+                    for (i in 0 until list.childCount) {
+                        if (findDayFlag) {
+                            break
+                        }
+                        val item = list.getChild(childCount - 1 - i)
+                        if (AccessibilityUtil.findOnceByText(item, "午") != null) {
+                            findDayFlag = true
+                        }
+                        if (item != null && item.childCount > 0) {
+                            LogUtils.d("点击群发")
+                            AccessibilityUtil.clickByNode(
+                                WeworkController.weworkService,
+                                AccessibilityUtil.findOnceByClazz(item, Views.FrameLayout)
+                            )
+                            if (!AccessibilityExtraUtil.loadingPage("EnterpriseCustomerEnterpriseMassMessageDetailActivity")) {
+                                AccessibilityUtil.clickByNode(
+                                    WeworkController.weworkService,
+                                    AccessibilityUtil.findOnceByClazz(
+                                        item,
+                                        Views.FrameLayout
+                                    )
+                                )
+                            }
+                            if (AccessibilityExtraUtil.loadingPage("EnterpriseCustomerEnterpriseMassMessageDetailActivity")) {
+                                AccessibilityUtil.findOneByText(getRoot(), "发送")
+                                sleep(Constant.POP_WINDOW_INTERVAL)
+                                if (AccessibilityUtil.findOnceByText(
+                                        getRoot(),
+                                        "已发送",
+                                        exact = true
+                                    ) != null
+                                ) {
+                                    LogUtils.d("该条群发已发送")
+                                    backPress()
+                                } else {
+                                    if (AccessibilityUtil.findTextAndClick(
+                                            getRoot(),
+                                            "发送",
+                                            exact = true
+                                        )
+                                    ) {
+                                        LogUtils.d("发送该条群发")
+                                        AccessibilityUtil.findOneByText(
+                                            getRoot(),
+                                            "已发送",
+                                            exact = true
+                                        )
+                                    }
+                                    backPress()
+                                    sleep(Constant.POP_WINDOW_INTERVAL)
+                                }
+                            }
+                        }
+                    }
+                    if (findDayFlag) {
+                        break
+                    }
+                    AccessibilityUtil.performScrollUp(list)
+                    sleep(Constant.CHANGE_PAGE_INTERVAL)
+                    list.refresh()
+                    LogUtils.d("向上滚动查找当日日期")
+                }
+            }
+            uploadCommandResult(
+                message,
+                ExecCallbackBean.SUCCESS,
+                "",
+                System.currentTimeMillis()
+            )
+
+        }
+
+        return true
+    }
+
 }

+ 3 - 0
app/src/main/java/org/yameida/worktool/service/WeworkService.kt

@@ -156,6 +156,9 @@ class WeworkService : AccessibilityService() {
                     ) + "...") else text
                 }"
             )
+            if ("{\"type\":11}"==text){
+                return
+            }
             try {
                 MyLooper.onMessage(webSocket, text)
             } catch (e: Exception) {

+ 9 - 3
app/src/main/java/org/yameida/worktool/utils/IWWAPIUtil.kt

@@ -2,7 +2,12 @@ package org.yameida.worktool.utils
 
 import android.content.Context
 import android.graphics.Bitmap
-import com.blankj.utilcode.util.*
+import com.blankj.utilcode.util.AppUtils
+import com.blankj.utilcode.util.FileUtils
+import com.blankj.utilcode.util.ImageUtils
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.SPUtils
+import com.blankj.utilcode.util.Utils
 import com.lzy.okgo.OkGo
 import com.tencent.wework.api.IWWAPI
 import com.tencent.wework.api.WWAPIFactory
@@ -14,7 +19,7 @@ import org.yameida.worktool.service.log
 import java.io.ByteArrayOutputStream
 import java.io.File
 import java.text.SimpleDateFormat
-import java.util.*
+import java.util.Date
 
 
 object IWWAPIUtil {
@@ -40,6 +45,7 @@ object IWWAPIUtil {
         title: String?,
         description: String?
     ): Boolean {
+//        init()
         val link = WWMediaLink()
         link.thumbUrl = thumbUrl
         link.webpageUrl = webpageUrl
@@ -49,7 +55,7 @@ object IWWAPIUtil {
         link.appName = AppUtils.getAppName()
         link.appId = appId
         link.agentId = agentId
-        return iwwapi?.sendMessage(link) ?: false
+        return iwwapi?.sendMessage(link)?: false
     }
 
     fun sendMicroProgram(

+ 115 - 1
app/src/main/java/org/yameida/worktool/utils/WeworkRoomUtil.kt

@@ -5,7 +5,12 @@ import com.blankj.utilcode.util.LogUtils
 import org.yameida.worktool.Constant
 import org.yameida.worktool.model.WeworkMessageBean
 import org.yameida.worktool.observer.MultiFileObserver
-import org.yameida.worktool.service.*
+import org.yameida.worktool.service.WeworkController
+import org.yameida.worktool.service.WeworkGetImpl
+import org.yameida.worktool.service.backPress
+import org.yameida.worktool.service.getRoot
+import org.yameida.worktool.service.goHome
+import org.yameida.worktool.service.sleep
 import org.yameida.worktool.utils.AccessibilityUtil.findAllOnceByClazz
 import org.yameida.worktool.utils.AccessibilityUtil.findFrontNode
 import org.yameida.worktool.utils.AccessibilityUtil.findOneByClazz
@@ -181,6 +186,115 @@ object WeworkRoomUtil {
     }
 
     /**
+     * 进入群发助手
+     */
+    fun intoGroupChat(title: String, fastIn: Boolean = true): Boolean {
+        if (checkRoom(title, strict = true)) {
+            return true
+        }
+        goHome()
+        val list = findOneByClazz(getRoot(), Views.RecyclerView, Views.ListView, Views.ViewGroup)
+        if (fastIn) {
+            if (list != null && list.childCount >= 2) {
+                for (i in 0 until list.childCount) {
+                    val item = list.getChild(i)
+                    val tvList = findAllOnceByClazz(item, Views.TextView).mapNotNull { it.text }
+                    if (tvList.isNotEmpty() && title == tvList[0].toString()) {
+                        intoRoomPreInit()
+                        AccessibilityUtil.performClick(item)
+                        LogUtils.d("快捷进入房间: $title")
+//                        AccessibilityUtil.waitForPageMissing(
+//                            "WwMainActivity",
+//                            "GlobalSearchActivity"
+//                        )
+//                        AccessibilityExtraUtil.loadingPage(
+//                            "ExternalGroupMessageListActivity",
+//                            "ExternalWechatUserMessageListActivity",
+//                            "MessageListActivity",
+//                            timeout = Constant.CHANGE_PAGE_INTERVAL
+//                        )
+//                        return checkRoom(title, strict = false)
+                        return true
+                    }
+                }
+            }
+        }
+        if (list != null) {
+            val frontNode = findFrontNode(list)
+            val textViewList = 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)
+                if (!AccessibilityExtraUtil.loadingPage("GlobalSearchActivityV2")) {
+                    LogUtils.e("未找到搜索页")
+                    return false
+                }
+                //消息页搜索结果列表
+                val selectListView = findOneByClazz(getRoot(), Views.RecyclerView)
+                val reverseRegexTitle = RegexHelper.reverseRegexTitle(trimTitle)
+                val regex1 =
+                    (if (Constant.friendRemarkStrict) "^$reverseRegexTitle" else "^(微信昵称:)|((企业)?邮箱:)?$reverseRegexTitle") +
+                            (if (needTrim) ".*?" else Constant.suffixString)
+                val regex2 = ".*?\\($reverseRegexTitle\\)$"
+                val regex = "($regex1)|($regex2)"
+                val searchResult = AccessibilityUtil.findAllByTextRegex(
+                    selectListView,
+                    regex,
+                    timeout = Constant.CHANGE_PAGE_INTERVAL * 3,
+                    root = false
+                )
+                if (searchResult.isNotEmpty()) {
+                    //过滤已退出的群聊
+                    val searchItem = searchResult.lastOrNull() {
+                        it.parent != null && it.parent.childCount < 3
+                    }
+                    if (searchItem != null) {
+                        intoRoomPreInit()
+                        AccessibilityUtil.performClick(searchItem)
+                        val text =
+                            AccessibilityUtil.findAllOnceByClazz(searchItem.parent, Views.TextView)
+                                .firstOrNull { it.text != null && !it.text.isNullOrBlank() }
+                        val title = text?.text?.toString() ?: title
+                        LogUtils.d("进入房间: $title")
+//                        AccessibilityUtil.waitForPageMissing(
+//                            "WwMainActivity",
+//                            "GlobalSearchActivityV2"
+//                        )
+//                        AccessibilityExtraUtil.loadingPage(
+//                            "ExternalGroupMessageListActivity",
+//                            "ExternalWechatUserMessageListActivity",
+//                            "MessageListActivity",
+//                            timeout = Constant.CHANGE_PAGE_INTERVAL
+//                        )
+//                        return checkRoom(title, strict = false)
+                        return true
+                    } else {
+                        LogUtils.e("搜索到已退出群聊")
+                    }
+                } else {
+                    LogUtils.e("未搜索到结果")
+                    val noResult = AccessibilityUtil.findOnceByText(
+                        getRoot(),
+                        "无搜索结果",
+                        "暂无搜索结果",
+                        exact = true
+                    ) != null
+                    LogUtils.e("企微: 无搜索结果: $noResult")
+                }
+            } else {
+                LogUtils.e("未找到搜索按钮")
+            }
+        } else {
+            LogUtils.e("未找到聊天列表")
+        }
+        return false
+    }
+
+    /**
      * 进入群管理页
      * @return true 成功进入群管理页
      */

+ 130 - 30
app/src/main/java/org/yameida/worktool/utils/WeworkTextUtil.kt

@@ -231,6 +231,7 @@ object WeworkTextUtil {
                     WeworkMessageBean.TEXT_TYPE_OFFICE
                 }
             }
+
             tvCount == 3 && ivCount == 1 -> {
                 if (isFileSize(tvList[1].text?.toString())) {
                     WeworkMessageBean.TEXT_TYPE_FILE
@@ -238,6 +239,7 @@ object WeworkTextUtil {
                     WeworkMessageBean.TEXT_TYPE_LINK
                 }
             }
+
             tvCount == 3 && ivCount == 2 -> WeworkMessageBean.TEXT_TYPE_MICROPROGRAM
             tvCount == 2 && ivCount == 0 -> WeworkMessageBean.TEXT_TYPE_CHAT_RECORD
             tvCount == 6 && ivCount == 0 -> WeworkMessageBean.TEXT_TYPE_COLLECTION
@@ -250,6 +252,7 @@ object WeworkTextUtil {
                     WeworkMessageBean.TEXT_TYPE_LINK
                 }
             }
+
             tvCount == 4 && ivCount == 2 -> WeworkMessageBean.TEXT_TYPE_VOICE
             tvCount == 5 && ivCount == 1 -> WeworkMessageBean.TEXT_TYPE_CARD
             tvCount == 1 && ivCount == 2 -> {
@@ -258,6 +261,7 @@ object WeworkTextUtil {
                 else
                     WeworkMessageBean.TEXT_TYPE_LOCATION
             }
+
             tvCount == 3 && ivCount == 0 -> WeworkMessageBean.TEXT_TYPE_REPLY
             tvCount == 0 && ivCount == 0 -> WeworkMessageBean.TEXT_TYPE_NOTIFY_ROBOT
             tvCount == 1 && ivCount == 3 -> WeworkMessageBean.TEXT_TYPE_CHANNELS_VIDEO
@@ -272,13 +276,18 @@ object WeworkTextUtil {
     fun getTextTypeFromItem(node: AccessibilityNodeInfo?, isGroup: Boolean = true): Int {
         if (node == null) return WeworkMessageBean.TEXT_TYPE_UNKNOWN
         //消息主体
-        val relativeLayoutItem = AccessibilityUtil.findOnceByClazz(node, Views.RelativeLayout, limitDepth = 1)
+        val relativeLayoutItem =
+            AccessibilityUtil.findOnceByClazz(node, Views.RelativeLayout, limitDepth = 1)
         if (relativeLayoutItem != null && relativeLayoutItem.childCount >= 2) {
             if (Views.ImageView.equals(relativeLayoutItem.getChild(relativeLayoutItem.childCount - 2).className)) {
                 LogUtils.v("头像在左边 本条消息发送者为其他联系人")
                 var textType = WeworkMessageBean.TEXT_TYPE_UNKNOWN
                 val relativeLayoutContent =
-                    AccessibilityUtil.findOnceByClazz(relativeLayoutItem, Views.RelativeLayout, limitDepth = 2)
+                    AccessibilityUtil.findOnceByClazz(
+                        relativeLayoutItem,
+                        Views.RelativeLayout,
+                        limitDepth = 2
+                    )
                 if (relativeLayoutContent != null) {
                     textType = getTextType(relativeLayoutContent)
                     LogUtils.v("textType: $textType")
@@ -305,7 +314,8 @@ object WeworkTextUtil {
     private fun getSender(node: AccessibilityNodeInfo?, isGroup: Boolean = true): Int {
         if (node == null) return WeworkMessageBean.TEXT_TYPE_UNKNOWN
         //消息主体
-        val relativeLayoutItem = AccessibilityUtil.findOnceByClazz(node, Views.RelativeLayout, limitDepth = 1)
+        val relativeLayoutItem =
+            AccessibilityUtil.findOnceByClazz(node, Views.RelativeLayout, limitDepth = 1)
         if (relativeLayoutItem != null && relativeLayoutItem.childCount >= 2) {
             if (Views.ImageView.equals(relativeLayoutItem.getChild(0).className)) {
                 return 0
@@ -379,7 +389,8 @@ object WeworkTextUtil {
             val item = node.getChild(node.childCount - 1 - i) ?: continue
             val nameList = getNameList(item)
             if (nameList.isEmpty()) {
-                val backNode = getMessageListNode(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT)
+                val backNode =
+                    getMessageListNode(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT)
                 if (backNode != null) {
                     val textTypeFromItem = getTextTypeFromItem(item)
                     val sender = getSender(item)
@@ -387,49 +398,97 @@ object WeworkTextUtil {
                         continue
                     }
                     if ((replyTextType == WeworkMessageBean.TEXT_TYPE_IMAGE)
-                        && (replyTextType == textTypeFromItem)) {
+                        && (replyTextType == textTypeFromItem)
+                    ) {
                         LogUtils.d("nameList: $nameList\nreplyContent: $replyContent")
-                        return longClickMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT, *key)
+                        return longClickMessageItem(
+                            item,
+                            WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT,
+                            *key
+                        )
                     }
                     if ((replyTextType == WeworkMessageBean.TEXT_TYPE_FILE || replyTextType == WeworkMessageBean.TEXT_TYPE_VIDEO)
-                        && replyContent.contains("###")) {
+                        && replyContent.contains("###")
+                    ) {
                         val replyContentList = replyContent.split("###")
                         if (AccessibilityUtil.findOnceByText(backNode, replyContentList[0]) != null
-                            && AccessibilityUtil.findOnceByText(backNode, replyContentList[1]) != null) {
+                            && AccessibilityUtil.findOnceByText(
+                                backNode,
+                                replyContentList[1]
+                            ) != null
+                        ) {
                             LogUtils.d("nameList: $nameList\nreplyContent: $replyContent")
-                            return longClickMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, *key)
+                            return longClickMessageItem(
+                                item,
+                                WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP,
+                                *key
+                            )
                         }
                     }
                     val textNode = AccessibilityUtil.findOnceByText(backNode, replyContent)
                     if (textNode != null && replyContent.isNotEmpty()) {
                         LogUtils.d("nameList: $nameList\nreplyContent: $replyContent")
-                        return longClickMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT, *key)
+                        return longClickMessageItem(
+                            item,
+                            WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT,
+                            *key
+                        )
                     }
                 }
             }
             for (name in nameList) {
                 if (name == replyNick) {
-                    val backNode = getMessageListNode(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP)
+                    val backNode =
+                        getMessageListNode(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP)
                     if (backNode != null) {
                         val textTypeFromItem = getTextTypeFromItem(item)
                         if ((replyTextType == WeworkMessageBean.TEXT_TYPE_IMAGE)
-                            && (replyTextType == textTypeFromItem)) {
+                            && (replyTextType == textTypeFromItem)
+                        ) {
+                            LogUtils.d("nameList: $nameList\nreplyContent: $replyContent")
+                            return longClickMessageItem(
+                                item,
+                                WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP,
+                                *key
+                            )
+                        }
+                        if (replyTextType == WeworkMessageBean.TEXT_TYPE_LINK && replyTextType == textTypeFromItem) {
                             LogUtils.d("nameList: $nameList\nreplyContent: $replyContent")
-                            return longClickMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, *key)
+                            return longClickMessageItem(
+                                item,
+                                WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP,
+                                *key
+                            )
                         }
                         if ((replyTextType == WeworkMessageBean.TEXT_TYPE_FILE || replyTextType == WeworkMessageBean.TEXT_TYPE_VIDEO)
-                            && replyContent.contains("###")) {
+                            && replyContent.contains("###")
+                        ) {
                             val replyContentList = replyContent.split("###")
-                            if (AccessibilityUtil.findOnceByText(backNode, replyContentList[0]) != null
-                                && AccessibilityUtil.findOnceByText(backNode, replyContentList[1]) != null) {
+                            if (AccessibilityUtil.findOnceByText(
+                                    backNode,
+                                    replyContentList[0]
+                                ) != null
+                                && AccessibilityUtil.findOnceByText(
+                                    backNode,
+                                    replyContentList[1]
+                                ) != null
+                            ) {
                                 LogUtils.d("nameList: $nameList\nreplyContent: $replyContent")
-                                return longClickMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, *key)
+                                return longClickMessageItem(
+                                    item,
+                                    WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP,
+                                    *key
+                                )
                             }
                         }
                         val textNode = AccessibilityUtil.findOnceByText(backNode, replyContent)
                         if (textNode != null && replyContent.isNotEmpty()) {
                             LogUtils.d("nameList: $nameList\nreplyContent: $replyContent")
-                            return longClickMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, *key)
+                            return longClickMessageItem(
+                                item,
+                                WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP,
+                                *key
+                            )
                         }
                     }
                 }
@@ -462,19 +521,37 @@ object WeworkTextUtil {
                 val textType = getTextTypeFromItem(item)
                 if (replyTextType == WeworkMessageBean.TEXT_TYPE_UNKNOWN || replyTextType == textType) {
                     if (replyTextType == WeworkMessageBean.TEXT_TYPE_IMAGE) {
-                        return longClickMyMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT, key)
+                        return longClickMyMessageItem(
+                            item,
+                            WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT,
+                            key
+                        )
                     }
                     if ((replyTextType == WeworkMessageBean.TEXT_TYPE_FILE || replyTextType == WeworkMessageBean.TEXT_TYPE_VIDEO)
-                        && replyContent.contains("###")) {
+                        && replyContent.contains("###")
+                    ) {
                         val replyContentList = replyContent.split("###")
                         if (AccessibilityUtil.findOnceByText(frontNode, replyContentList[0]) != null
-                            && AccessibilityUtil.findOnceByText(frontNode, replyContentList[1]) != null) {
-                            return longClickMyMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, key)
+                            && AccessibilityUtil.findOnceByText(
+                                frontNode,
+                                replyContentList[1]
+                            ) != null
+                        ) {
+                            return longClickMyMessageItem(
+                                item,
+                                WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP,
+                                key
+                            )
                         }
                     }
-                    val textNode = AccessibilityUtil.findOnceByText(frontNode, replyContent, exact = true)
+                    val textNode =
+                        AccessibilityUtil.findOnceByText(frontNode, replyContent, exact = true)
                     if (textNode != null && replyContent.isNotEmpty()) {
-                        return longClickMyMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT, key)
+                        return longClickMyMessageItem(
+                            item,
+                            WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT,
+                            key
+                        )
                     }
                 }
             }
@@ -482,7 +559,11 @@ object WeworkTextUtil {
         return false
     }
 
-    private fun longClickMessageItem(item: AccessibilityNodeInfo, roomType: Int, vararg key: String): Boolean {
+    private fun longClickMessageItem(
+        item: AccessibilityNodeInfo,
+        roomType: Int,
+        vararg key: String
+    ): Boolean {
         val backNode = getMessageListNode(item, roomType)
         if ("单击" in key) {
             return AccessibilityUtil.clickByNode(WeworkController.weworkService, backNode)
@@ -500,7 +581,11 @@ object WeworkTextUtil {
         return false
     }
 
-    private fun longClickMyMessageItem(item: AccessibilityNodeInfo, roomType: Int, key: String): Boolean {
+    private fun longClickMyMessageItem(
+        item: AccessibilityNodeInfo,
+        roomType: Int,
+        key: String
+    ): Boolean {
         val frontNode = getMyMessageListNode(item)
         if (key == "单击") {
             return AccessibilityUtil.clickByNode(WeworkController.weworkService, frontNode)
@@ -512,7 +597,11 @@ object WeworkTextUtil {
             val keyTv = AccessibilityUtil.findOnceByText(optionRv, key, exact = true)
             if (keyTv != null) {
                 AccessibilityUtil.performClick(keyTv)
-                if (AccessibilityExtraUtil.loadingPage("CustomDialog", timeout = Constant.POP_WINDOW_INTERVAL)) {
+                if (AccessibilityExtraUtil.loadingPage(
+                        "CustomDialog",
+                        timeout = Constant.POP_WINDOW_INTERVAL
+                    )
+                ) {
                     AccessibilityUtil.findTextAndClick(getRoot(), "确定", "我知道了", exact = true)
                 }
                 return true
@@ -526,13 +615,24 @@ object WeworkTextUtil {
      * 适用于左侧发言者
      * @param item 消息item节点
      */
-    private fun getMessageListNode(item: AccessibilityNodeInfo, roomType: Int): AccessibilityNodeInfo? {
-        if (roomType in arrayOf(WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT, WeworkMessageBean.ROOM_TYPE_EXTERNAL_CONTACT)) {
+    private fun getMessageListNode(
+        item: AccessibilityNodeInfo,
+        roomType: Int
+    ): AccessibilityNodeInfo? {
+        if (roomType in arrayOf(
+                WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT,
+                WeworkMessageBean.ROOM_TYPE_EXTERNAL_CONTACT
+            )
+        ) {
             val node = AccessibilityUtil.findOnceByClazz(item, Views.ImageView)
             if (node != null) {
                 return AccessibilityUtil.findBackNode(node)
             }
-        } else if (roomType in arrayOf(WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, WeworkMessageBean.ROOM_TYPE_EXTERNAL_GROUP)) {
+        } else if (roomType in arrayOf(
+                WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP,
+                WeworkMessageBean.ROOM_TYPE_EXTERNAL_GROUP
+            )
+        ) {
             val node = AccessibilityUtil.findOnceByClazz(item, Views.ViewGroup)
             if (node != null) {
                 return AccessibilityUtil.findBackNode(node)

+ 1 - 1
build.gradle

@@ -11,7 +11,7 @@ buildscript {
         maven { url 'https://jitpack.io' }
     }
     dependencies {
-        classpath "com.android.tools.build:gradle:3.5.4"
+        classpath "com.android.tools.build:gradle:4.0.0"
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
 
         // NOTE: Do not place your application dependencies here; they belong

+ 1 - 1
gradle/wrapper/gradle-wrapper.properties

@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip