This commit is contained in:
2026-05-11 14:32:27 +08:00
parent c6fbcec2d6
commit 473e2c2d89
11 changed files with 165 additions and 137 deletions

View File

@@ -58,7 +58,7 @@ object Constant {
SPUtils.getInstance().put(weworkCorpName + "weworkMP", value) SPUtils.getInstance().put(weworkCorpName + "weworkMP", value)
} }
var encryptType: Int = SPUtils.getInstance().getInt("encryptType", 0) var encryptType: Int = SPUtils.getInstance().getInt("encryptType", 0)
var autoReply: Int = SPUtils.getInstance().getInt("autoReply", 1) var autoReply: Int = SPUtils.getInstance().getInt("autoReply", 0)
var groupStrict: Boolean var groupStrict: Boolean
get() = SPUtils.getInstance().getBoolean("groupStrict", false) get() = SPUtils.getInstance().getBoolean("groupStrict", false)
set(value) = SPUtils.getInstance().put("groupStrict", value) set(value) = SPUtils.getInstance().put("groupStrict", value)

View File

@@ -1,56 +0,0 @@
package org.yameida.worktool
import com.blankj.utilcode.util.TimeUtils
import org.yameida.worktool.model.WeworkMessageBean
import org.yameida.worktool.service.MyLooper
import org.yameida.worktool.service.WeworkController
import org.yameida.worktool.service.WeworkLoopImpl
import org.yameida.worktool.service.getRoot
import org.yameida.worktool.utils.AccessibilityUtil
import java.util.*
/**
* 示例
*/
object Demo {
fun test(flag: Boolean) {
if (!flag) return
MyLooper.getInstance().removeCallbacksAndMessages(null)
//打印当前视图树
// AccessibilityUtil.printNodeClazzTree(getRoot())
}
fun test2(name: String) {
val time = TimeUtils.date2String(Date(), "MMddHHmm")
val groupName = "测试群$time"
val json = """
{
"socketType":2,
"list":[
{
"type":203,
"titleList":[
"$name"
],
"receivedContent":"你好~我是机器人,你可以@我和我聊天你也可以通过API文档来让我发送消息或完成建群等任务。接口文档https://www.apifox.cn/apidoc/project-1035094/api-23520034"
},
{
"type": 206,
"groupName": "$groupName",
"selectList": [
"$name",
"甲仑"
],
"groupAnnouncement": "(自动拉群+自动群公告) WorkTool欢迎大家~WorkTool管家是机器人有问题可以在QQ群反馈~@我可以聊天~"
}
]
}
""".trimIndent()
MyLooper.onMessage(null, json)
}
}

View File

@@ -22,6 +22,7 @@ import org.yameida.worktool.utils.*
import org.yameida.worktool.utils.capture.MediaProjectionHolder import org.yameida.worktool.utils.capture.MediaProjectionHolder
import org.yameida.worktool.utils.envcheck.CheckHook import org.yameida.worktool.utils.envcheck.CheckHook
import org.yameida.worktool.utils.envcheck.CheckRoot import org.yameida.worktool.utils.envcheck.CheckRoot
import kotlin.random.Random
class ListenActivity : AppCompatActivity() { class ListenActivity : AppCompatActivity() {
@@ -59,6 +60,7 @@ class ListenActivity : AppCompatActivity() {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
unregisterReceiver(openWsReceiver) unregisterReceiver(openWsReceiver)
FloatWindowHelper.hideWindow()
} }
override fun onResume() { override fun onResume() {
@@ -75,16 +77,11 @@ class ListenActivity : AppCompatActivity() {
iv_settings.setOnClickListener { iv_settings.setOnClickListener {
SettingsActivity.enterActivity(this) SettingsActivity.enterActivity(this)
} }
et_channel.setText(Constant.robotId) tv_channel.text = Constant.robotId.ifBlank { "未生成" }
bt_save.setOnClickListener { bt_reset_channel.setOnClickListener {
val channel = et_channel.text.toString().trim() val channel = generateDefaultRobotId()
Constant.robotId = channel tv_channel.text = channel
ToastUtils.showLong("保存成功") saveChannel(channel, toast = "重置成功")
sendBroadcast(Intent(Constant.WEWORK_NOTIFY).apply {
putExtra("type", "modify_channel")
})
HttpUtil.getMyConfig(toast = false)
MobclickAgent.onProfileSignIn(channel)
} }
tv_host.text = Constant.host tv_host.text = Constant.host
tv_host.setOnClickListener { tv_host.setOnClickListener {
@@ -185,13 +182,35 @@ class ListenActivity : AppCompatActivity() {
} }
private fun initData() { private fun initData() {
Constant.robotId = "2038521871751249921" // 链接号为空时自动生成一次并持久化,避免每次启动覆盖用户手动保存的链接号
Constant.host = "ws://8.166.130.74:18680" if (Constant.robotId.isBlank()) {
Constant.robotId = generateDefaultRobotId()
tv_channel.text = Constant.robotId
}
// HttpUtil.checkUpdate() // HttpUtil.checkUpdate()
HttpUtil.getMyConfig(toast = false) HttpUtil.getMyConfig(toast = false)
CacheUtil.autoDelete() CacheUtil.autoDelete()
} }
private fun generateDefaultRobotId(): String {
val suffix = Random.nextInt(1000, 9999)
return "${System.currentTimeMillis()}$suffix"
}
private fun saveChannel(channel: String, toast: String) {
if (channel.isBlank()) {
ToastUtils.showLong("链接号不能为空")
return
}
Constant.robotId = channel
ToastUtils.showLong(toast)
sendBroadcast(Intent(Constant.WEWORK_NOTIFY).apply {
putExtra("type", "modify_channel")
})
HttpUtil.getMyConfig(toast = false)
MobclickAgent.onProfileSignIn(channel)
}
private fun initNotification() { private fun initNotification() {
if (!Constant.enableMediaProject) { if (!Constant.enableMediaProject) {
return return
@@ -206,7 +225,7 @@ class ListenActivity : AppCompatActivity() {
}, Context.BIND_AUTO_CREATE) }, Context.BIND_AUTO_CREATE)
//开启屏幕录制权限 //开启屏幕录制权限
if (MediaProjectionHolder.mMediaProjection == null) { if (MediaProjectionHolder.mMediaProjection == null) {
bt_save.postDelayed({ window.decorView.postDelayed({
fastStartActivity(this, GetScreenShotActivity::class.java) fastStartActivity(this, GetScreenShotActivity::class.java)
}, 1000) }, 1000)
} }
@@ -282,8 +301,8 @@ class ListenActivity : AppCompatActivity() {
.setNegativeButton("", null) .setNegativeButton("", null)
.setPositiveButton("", null) .setPositiveButton("", null)
val show = positiveButton.show() val show = positiveButton.show()
bt_save.postDelayed({ show.dismiss() }, 5000) window.decorView.postDelayed({ show.dismiss() }, 5000)
bt_save.postDelayed({ window.decorView.postDelayed({
packageManager.getLaunchIntentForPackage(Constant.PACKAGE_NAMES)?.apply { packageManager.getLaunchIntentForPackage(Constant.PACKAGE_NAMES)?.apply {
this.flags = Intent.FLAG_ACTIVITY_NEW_TASK this.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(this) startActivity(this)

View File

@@ -1,7 +1,6 @@
package org.yameida.worktool.service package org.yameida.worktool.service
import com.blankj.utilcode.util.* import com.blankj.utilcode.util.*
import org.yameida.worktool.Demo
import org.yameida.worktool.annotation.RequestMapping import org.yameida.worktool.annotation.RequestMapping
import org.yameida.worktool.model.ExecCallbackBean import org.yameida.worktool.model.ExecCallbackBean
import org.yameida.worktool.model.WeworkMessageBean import org.yameida.worktool.model.WeworkMessageBean
@@ -159,7 +158,6 @@ object WeworkController {
@RequestMapping @RequestMapping
fun test(message: WeworkMessageBean? = null) { fun test(message: WeworkMessageBean? = null) {
LogUtils.d(message) LogUtils.d(message)
Demo.test(true)
} }
/** /**
@@ -640,4 +638,4 @@ object WeworkController {
return WeworkGetImpl.getCorpList(message) return WeworkGetImpl.getCorpList(message)
} }
} }

View File

@@ -7,7 +7,6 @@ import androidx.core.text.isDigitsOnly
import com.blankj.utilcode.util.* import com.blankj.utilcode.util.*
import com.hjq.toast.ToastUtils import com.hjq.toast.ToastUtils
import org.yameida.worktool.Constant import org.yameida.worktool.Constant
import org.yameida.worktool.Demo
import org.yameida.worktool.MyApplication import org.yameida.worktool.MyApplication
import org.yameida.worktool.activity.GetScreenShotActivity import org.yameida.worktool.activity.GetScreenShotActivity
import org.yameida.worktool.model.WeworkMessageBean import org.yameida.worktool.model.WeworkMessageBean
@@ -176,8 +175,7 @@ object WeworkLoopImpl {
val nameList = passFriendRequest() val nameList = passFriendRequest()
if (nameList.isEmpty()) if (nameList.isEmpty())
break break
//todo 可自定义执行任务 // todo 可自定义执行任务
// Demo.test2(nameList[0])
} }
} }
return true return true
@@ -1020,4 +1018,4 @@ object WeworkLoopImpl {
return false return false
} }
} }

View File

@@ -13,11 +13,9 @@ import okhttp3.Response
import okhttp3.WebSocket import okhttp3.WebSocket
import okhttp3.WebSocketListener import okhttp3.WebSocketListener
import org.yameida.worktool.Constant import org.yameida.worktool.Constant
import org.yameida.worktool.Demo
import org.yameida.worktool.observer.MultiFileObserver import org.yameida.worktool.observer.MultiFileObserver
import org.yameida.worktool.utils.* import org.yameida.worktool.utils.*
import java.lang.Exception import java.lang.Exception
import kotlin.concurrent.thread
/** /**
* 企业微信辅助服务 * 企业微信辅助服务
@@ -47,8 +45,6 @@ class WeworkService : AccessibilityService() {
MyLooper.init() MyLooper.init()
//初始化图片接收 //初始化图片接收
initObserver() initObserver()
//开发者可以在这里添加测试代码 启动时调用一次
thread { Demo.test(AppUtils.isAppDebug()) }
//监听是否修改链接号并重新长连接 //监听是否修改链接号并重新长连接
registerReceiver(object : BroadcastReceiver() { registerReceiver(object : BroadcastReceiver() {
@@ -116,6 +112,7 @@ class WeworkService : AccessibilityService() {
//隐藏软键盘模式 //隐藏软键盘模式
softKeyboardController.showMode = SHOW_MODE_AUTO softKeyboardController.showMode = SHOW_MODE_AUTO
webSocketManager.close(1000, "service Destroy") webSocketManager.close(1000, "service Destroy")
FloatWindowHelper.hideWindow()
} }
inner class EchoWebSocketListener : WebSocketListener() { inner class EchoWebSocketListener : WebSocketListener() {
@@ -172,4 +169,4 @@ class WeworkService : AccessibilityService() {
}) })
} }
} }
} }

View File

@@ -31,6 +31,7 @@ import kotlin.concurrent.thread
object FloatWindowHelper { object FloatWindowHelper {
var isPause = false var isPause = false
private var bound = false
fun showWindow() { fun showWindow() {
LogUtils.d("FloatWindowHelper.showWindow()") LogUtils.d("FloatWindowHelper.showWindow()")
@@ -39,7 +40,23 @@ object FloatWindowHelper {
val app = Utils.getApp() val app = Utils.getApp()
val intent = Intent(app, DefaultFloatService::class.java) val intent = Intent(app, DefaultFloatService::class.java)
app.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE) if (!bound) {
bound = app.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
}
}
fun hideWindow() {
LogUtils.d("FloatWindowHelper.hideWindow()")
val app = Utils.getApp()
FloatWindowManager.hide(DefaultFloatService::class.java)
if (bound) {
try {
app.unbindService(serviceConnection)
} catch (ignore: Exception) {
}
bound = false
}
app.stopService(Intent(app, DefaultFloatService::class.java))
} }
/** /**
@@ -143,7 +160,8 @@ object FloatWindowHelper {
override fun onServiceDisconnected(name: ComponentName?) { override fun onServiceDisconnected(name: ComponentName?) {
LogUtils.i("DefaultFloatService 服务断开") LogUtils.i("DefaultFloatService 服务断开")
bound = false
} }
} }
} }

View File

@@ -22,32 +22,37 @@ import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket; import okhttp3.WebSocket;
import okhttp3.WebSocketListener; import okhttp3.WebSocketListener;
import okio.ByteString;
public class WebSocketManager { public class WebSocketManager {
public static final String HEARTBEAT = "{\"type\":" + WeworkMessageBean.HEART_BEAT + "}"; public static final String HEARTBEAT = "{\"type\":" + WeworkMessageBean.HEART_BEAT + "}";
private static final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); private static final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
private static final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.pingInterval(25, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
public static Map<String, WebSocketManager> webSocketManager = new ConcurrentHashMap<>(); public static Map<String, WebSocketManager> webSocketManager = new ConcurrentHashMap<>();
private static final int reconnectInt = 5000; //毫秒
private static final long heartBeatRate = 5; //秒 private static final long heartBeatRate = 5; //秒
private Map<String, Long> messageIdMap = new ConcurrentHashMap<>(); private Map<String, Long> messageIdMap = new ConcurrentHashMap<>();
private ScheduledFuture task; private ScheduledFuture task;
private WebSocket socket; private WebSocket socket;
private String url; private String url;
private WebSocketListener listener; private WebSocketListener listener;
private boolean connecting = false; private volatile boolean connecting = false;
private volatile boolean manuallyClosed = false;
private volatile boolean opened = false;
private long lastConnectedTime = 0L; private long lastConnectedTime = 0L;
public WebSocketManager(String url, WebSocketListener listener) { public WebSocketManager(String url, WebSocketListener listener) {
Log.e(url, "新建链接"); Log.e(url, "新建链接");
this.url = url; this.url = url;
this.listener = listener; this.listener = listener;
OkHttpClient client = new OkHttpClient(); this.socket = client.newWebSocket(new Request.Builder().url(url).build(), innerListener);
Request request = new Request.Builder().url(url).build();
this.socket = client.newWebSocket(request, listener);
socket.send("{\"td\":" + System.currentTimeMillis() + "}");
webSocketManager.put(url, this); webSocketManager.put(url, this);
task = heartCheckStart(); task = heartCheckStart();
} }
@@ -83,7 +88,7 @@ public class WebSocketManager {
public void send(WeworkMessageListBean msg, boolean log) { public void send(WeworkMessageListBean msg, boolean log) {
String json = GsonUtils.toJson(msg); String json = GsonUtils.toJson(msg);
boolean success = socket.send(json); boolean success = socket != null && socket.send(json);
if (log && success) if (log && success)
LogUtils.d(url, json, "通讯消息发送成功!"); LogUtils.d(url, json, "通讯消息发送成功!");
if (!success) if (!success)
@@ -91,14 +96,19 @@ public class WebSocketManager {
} }
public void send(String msg) { public void send(String msg) {
boolean success = socket.send(msg); boolean success = socket != null && socket.send(msg);
LogUtils.e(url, msg, (success ? "通讯消息发送成功!" : "通讯消息发送失败!")); LogUtils.e(url, msg, (success ? "通讯消息发送成功!" : "通讯消息发送失败!"));
} }
public void close(int code, String reason) { public void close(int code, String reason) {
task.cancel(true); manuallyClosed = true;
if (task != null) {
task.cancel(true);
}
Log.e("url", "task 取消"); Log.e("url", "task 取消");
this.socket.close(code, reason); if (this.socket != null) {
this.socket.close(code, reason);
}
Log.e(url, "链接关闭"); Log.e(url, "链接关闭");
} }
@@ -112,44 +122,25 @@ public class WebSocketManager {
} }
public void reConnect() { public void reConnect() {
if (manuallyClosed || connecting) {
return;
}
connecting = true; connecting = true;
opened = false;
Log.e(url, "重连"); Log.e(url, "重连");
boolean isConnect = false; try {
int interval = reconnectInt; if (socket != null) {
while (true) { socket.cancel();
try {
isConnect = connect();
if (isConnect) {
connecting = false;
break;
}
} catch (Exception e) {
e.printStackTrace();
}
try {
Thread.sleep(interval);
if (interval < 600000) {
interval *= 2;
}
} catch (InterruptedException e) {
e.printStackTrace();
} }
} catch (Exception ignore) {
} }
} socket = client.newWebSocket(new Request.Builder().url(url).build(), innerListener);
private boolean connect() {
WebSocket s = new OkHttpClient().newWebSocket(new Request.Builder().url(url).build(), listener);
if (s.send(WebSocketManager.HEARTBEAT)) {
this.socket = s;
s.send("{\"td\":" + System.currentTimeMillis() + "}");
return true;
}
return false;
} }
private ScheduledFuture heartCheckStart() { private ScheduledFuture heartCheckStart() {
lastConnectedTime = System.currentTimeMillis(); lastConnectedTime = System.currentTimeMillis();
Runnable r = () -> { Runnable r = () -> {
if (manuallyClosed) return;
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
Log.d(url, "心跳检测" + df.format(new Date()));// new Date()为获取当前系统时间 Log.d(url, "心跳检测" + df.format(new Date()));// new Date()为获取当前系统时间
if (!connecting && (socket == null || !socket.send(HEARTBEAT))) { if (!connecting && (socket == null || !socket.send(HEARTBEAT))) {
@@ -159,7 +150,7 @@ public class WebSocketManager {
reConnect(); reConnect();
//重连后刷新连接时间 //重连后刷新连接时间
lastConnectedTime = System.currentTimeMillis(); lastConnectedTime = System.currentTimeMillis();
} else if (System.currentTimeMillis() % 1000 == 0) { } else if (opened && System.currentTimeMillis() % 1000 == 0) {
socket.send("{\"td\":" + System.currentTimeMillis() + "}"); socket.send("{\"td\":" + System.currentTimeMillis() + "}");
} }
if (!Constant.INSTANCE.getEnableMediaProject()) { if (!Constant.INSTANCE.getEnableMediaProject()) {
@@ -176,4 +167,46 @@ public class WebSocketManager {
public static WebSocketManager getWebSocketManager(String id) { public static WebSocketManager getWebSocketManager(String id) {
return webSocketManager.get(id); return webSocketManager.get(id);
} }
private final WebSocketListener innerListener = new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
opened = true;
connecting = false;
lastConnectedTime = System.currentTimeMillis();
listener.onOpen(webSocket, response);
}
@Override
public void onMessage(WebSocket webSocket, String text) {
listener.onMessage(webSocket, text);
}
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
listener.onMessage(webSocket, bytes);
}
@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
opened = false;
connecting = false;
listener.onClosing(webSocket, code, reason);
}
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
opened = false;
connecting = false;
listener.onClosed(webSocket, code, reason);
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
opened = false;
connecting = false;
WeworkController.INSTANCE.setEnableLoopRunning(false);
listener.onFailure(webSocket, t, response);
}
};
} }

View File

@@ -326,11 +326,14 @@
android:textColor="@color/color_333333" android:textColor="@color/color_333333"
android:textSize="@dimen/setting_start_font_size" /> android:textSize="@dimen/setting_start_font_size" />
<EditText <TextView
android:id="@+id/et_channel" android:id="@+id/tv_channel"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="请输入申请的机器人ID" android:paddingTop="4dp"
android:paddingBottom="4dp"
android:text="未生成"
android:textIsSelectable="true"
android:textColor="@color/color_999999" android:textColor="@color/color_999999"
android:textSize="@dimen/setting_end_font_size" /> android:textSize="@dimen/setting_end_font_size" />
@@ -344,17 +347,17 @@
android:orientation="vertical"> android:orientation="vertical">
<Button <Button
android:id="@+id/bt_save" android:id="@+id/bt_reset_channel"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:background="@drawable/comment_red_btn" android:background="@drawable/comment_red_btn"
android:paddingStart="50dp" android:paddingStart="42dp"
android:paddingEnd="50dp" android:paddingEnd="42dp"
android:textSize="18sp" android:text="重置"
android:textStyle="bold"
android:textColor="@color/white" android:textColor="@color/white"
android:text="保存" /> android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout> </LinearLayout>

View File

@@ -56,7 +56,14 @@ class DefaultFloatService : BaseFloatWindow(), View.OnClickListener {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
LogUtils.d(TAG, "onStartCommand: ${intent?.data}") LogUtils.d(TAG, "onStartCommand: ${intent?.data}")
show() when (intent?.getStringExtra(FloatWindowManager.EXTRA_ACTION)) {
FloatWindowManager.ACTION_HIDE -> {
hide()
stopForeground(true)
stopSelf()
}
else -> show()
}
return super.onStartCommand(intent, flags, startId) return super.onStartCommand(intent, flags, startId)
} }
@@ -197,4 +204,10 @@ class DefaultFloatService : BaseFloatWindow(), View.OnClickListener {
} }
override fun onDestroyed() {} override fun onDestroyed() {}
override fun onDestroy() {
hide()
stopForeground(true)
super.onDestroy()
}
} }

View File

@@ -11,9 +11,13 @@ object FloatWindowManager {
private val TAG = FloatWindowManager::class.java.simpleName private val TAG = FloatWindowManager::class.java.simpleName
private var context = Utils.getApp() private var context = Utils.getApp()
const val EXTRA_ACTION = "float_action"
const val ACTION_SHOW = "show"
const val ACTION_HIDE = "hide"
fun show(service: Class<out BaseFloatWindow>, intent: Intent? = null) { fun show(service: Class<out BaseFloatWindow>, intent: Intent? = null) {
startServiceSafe(Intent(context, service).apply { startServiceSafe(Intent(context, service).apply {
putExtra(EXTRA_ACTION, ACTION_SHOW)
if (intent != null) { if (intent != null) {
this.putExtras(intent) this.putExtras(intent)
} }
@@ -22,6 +26,7 @@ object FloatWindowManager {
fun hide(service: Class<out BaseFloatWindow>, intent: Intent? = null) { fun hide(service: Class<out BaseFloatWindow>, intent: Intent? = null) {
startServiceSafe(Intent(context, service).apply { startServiceSafe(Intent(context, service).apply {
putExtra(EXTRA_ACTION, ACTION_HIDE)
if (intent != null) { if (intent != null) {
this.putExtras(intent) this.putExtras(intent)
} }
@@ -36,4 +41,4 @@ object FloatWindowManager {
} }
} }
} }