上传备份

master
王兵 3 weeks ago
parent 64f9b0f813
commit 667366fdf5

@ -1,6 +1,7 @@
package xyz.wbsite.jmacro;
import cn.hutool.core.util.RandomUtil;
import org.sikuli.basics.Settings;
import org.sikuli.script.FindFailed;
import org.sikuli.script.Image;
import org.sikuli.script.KeyModifier;
@ -13,8 +14,10 @@ import org.sikuli.script.Screen;
import xyz.wbsite.jmacro.base.ColorLocation;
import xyz.wbsite.jmacro.base.Legend;
import xyz.wbsite.jmacro.util.Logger;
import xyz.wbsite.jmacro.util.MousePathUtil;
import xyz.wbsite.jmacro.util.TaskUtil;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
@ -48,6 +51,12 @@ public abstract class JMacro {
public JMacro() {
this.startFocus();
// 禁止漂移,所有拟人操作都提前计算好路径,此处的随机要关闭
Mouse.setRandom(0);
// 鼠标移动延迟(秒),默认值约为 0.5
Settings.MoveMouseDelay = 0.0f;
// 忽略鼠标移动事件
Mouse.setMouseMovedAction(0);
}
public boolean isRun() {
@ -137,7 +146,43 @@ public abstract class JMacro {
* @param location
*/
public void mouseMove(Location location) {
Mouse.move(location);
List<int[]> path = MousePathUtil.generateBezierPath(Mouse.at(), location);
Settings.MoveMouseDelay = 0.0f;
// 暂停点索引(-1为不暂停
int pause = -1;
// 通过概率决断本次是否需要产生暂停
if (RandomUtil.randomInt(0, 100) < 50) {
int startIdx = path.size() / 3;
int endIdx = 2 * path.size() / 3;
pause = RandomUtil.randomInt(startIdx, endIdx);
}
for (int[] p : path) {
delay(7 + RandomUtil.randomInt(-3, 3));
if (pause == path.indexOf(p)) {
Logger.info("拟人操作,停顿片刻", pause);
delay(200 + RandomUtil.randomInt(-100, 200));
}
Mouse.move(new Location(p[0], p[1]));
}
}
/**
*
* <p>
*
*
* @param region
*/
public void mouseMove(Region region) {
// 随机移动到区域中心附近(而非直接移动到中心)
Location center = region.getCenter();
// 得到随机移动到区域中心附近的半径
int radius = (int) (Math.min(region.getW(), region.getH()) / 2 * 0.9f);
int x = center.getX() + RandomUtil.randomInt(-radius, radius);
int y = center.getY() + RandomUtil.randomInt(-radius, radius);
mouseMove(new Location(x, y));
}
/**

@ -7,15 +7,21 @@ import cn.hutool.core.util.StrUtil;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.util.Duration;
import org.sikuli.script.Region;
import xyz.wbsite.jmacro.base.Legend;
@ -49,12 +55,18 @@ public class JMainController implements Initializable {
@FXML
private Button stop;
@FXML
private ImageView set;
private ToggleGroup runMode;
@FXML
private HBox modeLoop;
@FXML
private HBox modeTiming;
@FXML
private TextField interval;
@FXML
private TextField times;
@FXML
private TextField timing;
@FXML
private ImageView preview;
@FXML
private TextArea console;
@ -62,7 +74,7 @@ public class JMainController implements Initializable {
private final int MAX_LENGTH = 100;
private final BoundedPriorityQueue<String> logs = new BoundedPriorityQueue<>(MAX_LENGTH);
private Semaphore semaphore = new Semaphore(1);
private final Semaphore semaphore = new Semaphore(1);
public static synchronized JMainController getInstance() {
return JMainController.instance;
@ -71,6 +83,26 @@ public class JMainController implements Initializable {
@Override
public void initialize(URL location, ResourceBundle resources) {
JMainController.instance = this;
runMode.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
@Override
public void changed(ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) {
String mode = ((RadioButton) newValue).getUserData().toString();
if ("loop".equals(mode)) {
modeLoop.setVisible(true);
modeLoop.setManaged(true);
modeTiming.setVisible(false);
modeTiming.setManaged(false);
}
if ("timing".equals(mode)) {
modeLoop.setVisible(false);
modeLoop.setManaged(false);
modeTiming.setVisible(true);
modeTiming.setManaged(true);
}
JProp.getInstance().setString("mode", mode);
}
});
// 控件初始化
int intervalValue = JProp.getInstance().getInt("interval", 60);
this.interval.setText(String.valueOf(intervalValue));
@ -89,7 +121,60 @@ public class JMainController implements Initializable {
}
JProp.getInstance().setInt("times", Convert.toInt(this.times.getText()));
});
installTip(this.set, "扩展配置");
String timingValue = JProp.getInstance().getString("timing", "");
this.timing.setText(timingValue);
this.timing.textProperty().addListener((observable, oldValue, newValue) -> {
boolean isValidFormat = true;
String errorMessage = "时间格式错误支持的格式如09:30:00,9:35:00,10:45,9:5,8:30";
// 空输入视为无效
if (StrUtil.isEmpty(newValue)) {
isValidFormat = false;
} else {
// 分割多个时间点并逐个验证
String[] timeParts = newValue.split(",");
for (String part : timeParts) {
String trimmedPart = part.trim();
// 单个时间点格式验证 (支持 HH:mm:ss、H:mm:ss、HH:mm、H:mm、H:m)
if (!trimmedPart.matches("^\\d{1,2}:\\d{1,2}(?::\\d{1,2})?$")) {
isValidFormat = false;
break;
}
// 验证时分秒数值范围
String[] hms = trimmedPart.split(":");
int hour = Convert.toInt(hms[0], -1);
int minute = Convert.toInt(hms[1], -1);
int second = 0; // 默认秒为0当没有秒部分时
// 处理带秒的格式
if (hms.length == 3) {
second = Convert.toInt(hms[2], -1);
}
// 验证数值范围小时0-23分钟0-59秒0-59
if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59) {
isValidFormat = false;
errorMessage = "时间数值错误!时(0-23)、分(0-59)、秒(0-59)";
break;
}
}
}
// 设置样式和提示
if (!isValidFormat) {
timing.setStyle("-fx-text-fill: #ff0000; -fx-border-color: #ff0000; -fx-border-width: 1px;");
Tooltip errorTooltip = new Tooltip(errorMessage);
errorTooltip.setStyle("-fx-background-color: #fff0f0; -fx-text-fill: #ff0000;");
Tooltip.install(timing, errorTooltip);
} else {
timing.setStyle("-fx-text-fill: #000000; -fx-border-color: transparent; -fx-border-width: 1px;");
}
// 保存配置修正原代码中的this.times.getText()错误)
JProp.getInstance().setString("timing", this.timing.getText());
});
installTip(this.interval, "两次脚本执行的间隔时间(秒)");
installTip(this.times, "脚本执行的总次数0代表无限循环");
}
@ -170,6 +255,7 @@ public class JMainController implements Initializable {
synchronized (JMainController.class) {
this.start.setDisable(true);
this.stop.setDisable(false);
this.saveConfig();
Logger.info("启动服务");
if (!JMainService.getInstance().run) {
boolean start = JMainService.start();
@ -182,6 +268,17 @@ public class JMainController implements Initializable {
}
}
/**
*
*/
public void saveConfig() {
String string = this.runMode.selectedToggleProperty().getValue().getUserData().toString();
JProp.getInstance().setString("mode", string);
JProp.getInstance().setInt("times", Convert.toInt(this.times.getText()));
JProp.getInstance().setInt("interval", Convert.toInt(this.interval.getText()));
JProp.getInstance().setString("timing", this.timing.getText());
}
/**
*
*/
@ -199,6 +296,8 @@ public class JMainController implements Initializable {
}
Logger.info("服务停止成功");
this.preview.setImage(null);
} else {
Logger.info("服务未运行");
}
}
}

@ -1,12 +1,10 @@
package xyz.wbsite.jmacro;
import cn.hutool.core.collection.CollUtil;
import xyz.wbsite.jmacro.base.Legend;
import xyz.wbsite.jmacro.util.DialogUtil;
import xyz.wbsite.jmacro.util.Logger;
import java.io.File;
import java.util.List;
/**
* 线
@ -30,25 +28,20 @@ public class JMainService {
}
/**
*
*
*/
public boolean run;
private static JScheduler scheduler = new JScheduler();
/**
* 线
*
*/
public MacroTask macroTask = new MacroTask();
public boolean run;
/**
*
*/
private JMacro macro;
/**
*
*/
private int count;
/**
*
*/
@ -67,19 +60,39 @@ public class JMainService {
Legend.setDefaultBase(legendDir);
}
public MacroTask createTask() {
return new MacroTask();
}
public static boolean start() {
if (JMainService.getInstance().macro.getWorkRegion() == null) {
JMainService.getInstance().macro.startFocus();
JMainService service = JMainService.getInstance();
if (service.macro.getWorkRegion() == null) {
service.macro.startFocus();
DialogUtil.alert("未聚焦到视口,请稍后再试!");
return false;
}
if (JMainService.getInstance().run) {
if (service.run) {
Logger.info("服务已启动");
return false;
}
service.run = true;
// 控件初始化
String mode = JProp.getInstance().getString("mode", "loop");
if ("loop".equals(mode)) {
int intervalValue = JProp.getInstance().getInt("interval", 60);
int timesValue = JProp.getInstance().getInt("times", 1);
scheduler.schedule(service.createTask(), intervalValue, timesValue);
}
if ("timing".equals(mode)) {
String timingValue = JProp.getInstance().getString("timing", "");
if (timingValue.isEmpty()) {
DialogUtil.alert("请输入定时时间");
return false;
}
scheduler.schedule(service.createTask(), timingValue);
}
// 4. 指定每天固定时间点执行
List<String> times = CollUtil.newArrayList("08:00", "12:35", "18:10");
return true;
}
@ -104,6 +117,11 @@ public class JMainService {
*/
public class MacroTask implements Runnable {
/**
*
*/
private int count;
@Override
public void run() {
if (macro == null) {

@ -1,5 +1,7 @@
package xyz.wbsite.jmacro;
import xyz.wbsite.jmacro.util.Logger;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
@ -193,6 +195,9 @@ public class JScheduler {
tasksHolder.remove(future);
}
}
// 自动停止服务
JMainController.getInstance().onStop();
Logger.info("任务执行完毕");
}
}
}
@ -211,9 +216,9 @@ public class JScheduler {
// 3, 5);
//
// // 3. 多时间点任务(每天 08:30:00, 12:00:00, 18:00:00 执行)
scheduler.schedule(
() -> System.out.println("定时任务执行: " + LocalTime.now()),
"12:58:00,18:00:00");
// scheduler.schedule(
// () -> System.out.println("定时任务执行: " + LocalTime.now()),
// "09:36:00,18:00:00");
// 运行一段时间后关闭
try {

@ -0,0 +1,124 @@
package xyz.wbsite.jmacro.util;
import cn.hutool.core.util.RandomUtil;
import org.sikuli.basics.Settings;
import org.sikuli.script.Location;
import org.sikuli.script.Mouse;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class MousePathUtil {
private static final Random random = new Random();
/**
* ease-in-out cubic->->
* f(t) = 3t^2 - 2t^3, t [0,1]
*/
private static double easeInOut(double t) {
return 3 * t * t - 2 * t * t * t;
}
/**
* 线
*
* @param x0 X
* @param y0 Y
* @param cx X线
* @param cy Y
* @param x1 X
* @param y1 Y
* @return
*/
public static List<int[]> generateBezierPath(int x0, int y0, int cx, int cy, int x1, int y1) {
double distance = Math.hypot(x1 - x0, y1 - y0);
int steps = (int) (distance / 5); // 每5px一个点
steps = Math.max(20, Math.min(steps, 200)); // 限制范围
return generateBezierPath(x0, y0, cx, cy, x1, y1, steps);
}
/**
* 线
*
* @param start
* @param end
* @return
*/
public static List<int[]> generateBezierPath(Location start, Location end) {
int x0 = start.x, y0 = start.y;
int x1 = end.x, y1 = end.y;
// 随机生成控制点,增加不确定性(避免总是直线)
int controlX = (x0 + x1) / 2 + random.nextInt(200) - 100;
int controlY = (y0 + y1) / 2 + random.nextInt(200) - 100;
return generateBezierPath(x0, y0, controlX, controlY, x1, y1);
}
/**
* 线
*
* @param x0 X
* @param y0 Y
* @param x1 X
* @param y1 Y
* @return
*/
public static List<int[]> generateBezierPath(int x0, int y0, int x1, int y1) {
// 随机生成控制点,增加不确定性(避免总是直线)
int controlX = (x0 + x1) / 2 + random.nextInt(200) - 100;
int controlY = (y0 + y1) / 2 + random.nextInt(200) - 100;
return generateBezierPath(x0, y0, controlX, controlY, x1, y1);
}
/**
* 线
*
* @param x0 X
* @param y0 Y
* @param cx X线
* @param cy Y
* @param x1 X
* @param y1 Y
* @param steps
* @return
*/
public static List<int[]> generateBezierPath(int x0, int y0, int cx, int cy, int x1, int y1, int steps) {
List<int[]> path = new ArrayList<>();
for (int i = 0; i <= steps; i++) {
// 使用ease函数进行非均匀采样
double t = (double) i / steps;
double easedT = easeInOut(t);
// 二次贝塞尔公式
double xt = Math.pow(1 - easedT, 2) * x0 + 2 * (1 - easedT) * easedT * cx + Math.pow(easedT, 2) * x1;
double yt = Math.pow(1 - easedT, 2) * y0 + 2 * (1 - easedT) * easedT * cy + Math.pow(easedT, 2) * y1;
// 加入轻微手抖效果
// xt += random.nextGaussian() * 0.5;
// yt += random.nextGaussian() * 0.5;
path.add(new int[]{(int) Math.round(xt), (int) Math.round(yt)});
}
return path;
}
public static void main(String[] args) throws InterruptedException {
int startX = 0, startY = 0;
int endX = 500, endY = 500;
List<int[]> path = generateBezierPath(startX, startY, endX, endY);
Settings.MoveMouseDelay = 0.0f;
Mouse.move(new Location(0, 0));
// 打印路径点
for (int[] p : path) {
System.out.println(p[0] + "," + p[1]);
Mouse.move(new Location(p[0], p[1]));
Thread.sleep(20);
}
}
}

@ -38,24 +38,24 @@ public class TaskImpl extends JMacro {
Logger.info("启动图标坐标:{}", launch.getRect().toString());
Logger.info("移动鼠标");
mouseMove(launch.getCenter());
Logger.info("双击我的电脑");
mouseLeftDoubleClick(launch);
Logger.info("等待程序启动中,请稍等...");
delay(3 * 1000);
Region windows = findLegend("我的电脑窗口", 0.9);
if (windows == null) {
Logger.error("我的电脑启动失败");
return;
}
Logger.info("定位到我的电脑窗口");
Logger.info("移动鼠标");
mouseMove(windows.getCenter().offset(100,0));
Logger.info("等待1秒后自动关闭");
delay(1000);
mouseLeftClick(windows.getCenter().offset(100,0));
Logger.info("结束任务");
// Logger.info("双击我的电脑");
// mouseLeftDoubleClick(launch);
// Logger.info("等待程序启动中,请稍等...");
// delay(3 * 1000);
//
// Region windows = findLegend("我的电脑窗口", 0.9);
// if (windows == null) {
// Logger.error("我的电脑启动失败");
// return;
// }
// Logger.info("定位到我的电脑窗口");
// Logger.info("移动鼠标");
// mouseMove(windows.getCenter().offset(100,0));
//
// Logger.info("等待1秒后自动关闭");
// delay(1000);
// mouseLeftClick(windows.getCenter().offset(100,0));
//
// Logger.info("结束任务");
}
}

@ -4,11 +4,11 @@
<?import javafx.geometry.Rectangle2D?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.Cursor?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
@ -38,25 +38,25 @@
</HBox>
<HBox alignment="CENTER_LEFT">
<children>
<Label contentDisplay="CENTER" prefHeight="25.0" text="执行选项"/>
<ImageView fx:id="set" fitHeight="12.0" fitWidth="12.0" preserveRatio="true">
<image>
<Image url="@set.png"/>
</image>
<Label contentDisplay="CENTER" prefHeight="25.0" text="执行模式"/>
<RadioButton mnemonicParsing="false" text="循环" selected="true" userData="loop">
<HBox.margin>
<Insets left="5.0"/>
</HBox.margin>
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
</ImageView>
<toggleGroup>
<ToggleGroup fx:id="runMode"/>
</toggleGroup>
</RadioButton>
<RadioButton mnemonicParsing="false" text="定时" toggleGroup="$runMode" userData="timing">
<HBox.margin>
<Insets left="5.0"/>
</HBox.margin>
</RadioButton>
</children>
</HBox>
<HBox alignment="CENTER_LEFT" prefHeight="25.0" prefWidth="200.0">
<HBox fx:id="modeLoop" alignment="CENTER_LEFT" prefHeight="25.0" prefWidth="200.0">
<children>
<Label text="每"/>
<TextField fx:id="interval" alignment="CENTER" prefWidth="35.0" promptText="时间" text="60">
@ -75,6 +75,20 @@
</children>
</HBox>
<HBox fx:id="modeTiming" visible="false" managed="false" alignment="CENTER_LEFT" prefHeight="25.0"
prefWidth="200.0">
<children>
<Label text="在"/>
<TextField fx:id="timing" prefWidth="150.0" promptText="如8:30:20,9:00"
text="8:30:20,9:00">
<HBox.margin>
<Insets left="3.0" right="3.0"/>
</HBox.margin>
</TextField>
<Label text="执行"/>
</children>
</HBox>
<Label contentDisplay="CENTER" prefHeight="25.0" text="辅助工具"/>
<HBox alignment="CENTER_LEFT" prefHeight="35.0">

Loading…
Cancel
Save

Powered by TurnKey Linux.