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.Key; import org.sikuli.script.KeyModifier; import org.sikuli.script.Location; import org.sikuli.script.Match; import org.sikuli.script.Mouse; import org.sikuli.script.Pattern; import org.sikuli.script.Region; 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.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.util.List; import java.util.concurrent.TimeUnit; /** * 脚本 * * @author wangbing * @version 0.0.1 * @since 1.8 */ public abstract class JMacro { /** * 脚本执行状态 */ private boolean run; /** * 默认超时秒数 */ private int defaultTimeOut = 10; /** * 工作区域 */ protected Region workRegion; /** * 屏幕对象 */ protected Screen screen = new Screen(); public JMacro() { this.startFocus(); // 禁止漂移,所有拟人操作都提前计算好路径,此处的随机要关闭 Mouse.setRandom(0); // 鼠标移动延迟(秒),默认值约为 0.5 Settings.MoveMouseDelay = 0.0f; // 忽略鼠标移动事件 Mouse.setMouseMovedAction(0); } public boolean isRun() { return run; } public void setRun(boolean run) { this.run = run; } public int getDefaultTimeOut() { return defaultTimeOut; } public void setDefaultTimeOut(int defaultTimeOut) { this.defaultTimeOut = defaultTimeOut; } /** * 开始聚焦 */ public void startFocus() { TaskUtil.asyncTask(() -> { workRegion = TaskUtil.timeTask(this::focus, defaultTimeOut, TimeUnit.SECONDS); if (workRegion != null) { Logger.info("聚焦成功"); } else { Logger.error("聚焦失败"); } }); } /** * 获取工作区域 * * @return 工作区域 */ public abstract Region focus(); /** * 执行脚本 */ public void start() { if (this.run) { Logger.error("脚本正在运行中"); return; } this.run = true; this.task(); this.stop(); } /** * 执行脚本 */ public abstract void task(); /** * 停止脚本 */ public void stop() { if (!this.run) { Logger.error("脚本未运行"); return; } this.run = false; } /** * 键盘按键组 */ public void keyInput(String keys) { this.screen.type(keys); } /** * 键盘按键 * keycode Key to press (e.g. KeyEvent.VK_A) */ public void keyPress(String key) { this.screen.type(key); } /** * 鼠标移动 * * @param location 坐标点 */ public void mouseMove(Location location) { List 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])); } } /** * 鼠标移动到这个区域 *

* 增加拟人操作,随机移动鼠标到区域中心附近(而非直接移动到中心) * * @param region 区域 */ public void mouseMove(Region region) { mouseMove(randomCenter(region)); } /** * 鼠标左键单击 */ public void mouseLeftClick() { Mouse.at().click(); } /** * 鼠标左键单击 * * @param region 区域 */ public void mouseLeftClick(Region region) { mouseMove(randomCenter(region)); mouseLeftClick(); } /** * 鼠标左键单击 * * @param location 坐标点 */ public void mouseLeftClick(Location location) { mouseMove(location); mouseLeftClick(); } /** * 鼠标左键双击 */ public void mouseLeftDoubleClick() { Mouse.at().doubleClick(); } /** * 鼠标左键双击 */ public void mouseLeftDoubleClick(Region region) { mouseMove(randomCenter(region)); mouseLeftDoubleClick(); } /** * 鼠标左键双击 */ public void mouseLeftDoubleClick(Location location) { mouseMove(location); mouseLeftDoubleClick(); } /** * 鼠标右键单击 */ public void mouseRightClick() { mouseRightClick(Mouse.at()); } /** * 鼠标右键单击 * * @param region 区域 */ public void mouseRightClick(Region region) { mouseMove(randomCenter(region)); mouseRightClick(); } /** * 鼠标右键单击 * * @param location 坐标点 */ public void mouseRightClick(Location location) { mouseMove(location); mouseRightClick(); } /** * 插入剪贴板 */ public void insertClipboard(String text) { StringSelection selection = new StringSelection(text); // 获取系统剪贴板 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); // 将文本设置到剪贴板 clipboard.setContents(selection, null); } /** * 执行复制命令 */ public void sendCopyCommand() { screen.type("c", KeyModifier.CTRL); } /** * 执行粘贴命令 */ public void sendPasteCommand() { screen.type("v", KeyModifier.CTRL); } /** * 执行回车命令 */ public void sendEnterCommand() { screen.type(Key.ENTER); } /** * 捕获指定区域屏幕 */ public Image capture(Region region) { return region.getImage(); } /** * 获取聚焦区域 */ public Region getWorkRegion() { return workRegion; } /** * 获取聚焦区域 */ public Region getScreenRect() { if (workRegion != null) { return workRegion; } return screen; } /** * 延迟 */ public void delay() { delay(100); } /** * 延迟 */ public void delay(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { throw new RuntimeException(e); } } /** * 抖动延迟 */ public void delayUnstable() { delayUnstable(800); } /** * 抖动延迟 */ public void delayUnstable(long millis) { if (millis < 200) { delay(millis); return; } delay(RandomUtil.randomLong(millis - 100, millis + 100)); } /** * 查找图片 * * @param region 区域范围(不设时,取全屏) * @param minSimilar 相似度 * @return 匹配区域 */ public Match findPic(Region region, String legend, double minSimilar) { Pattern pattern = new Pattern(legend).similar(minSimilar); return region.findBest(pattern); } /** * 查找图片 * * @param region 区域范围(不设时,取全屏) * @param minSimilar 相似度 * @return 匹配区域 */ public boolean clickPic(Region region, String legend, double minSimilar) { try { Pattern pattern = new Pattern(legend).similar(minSimilar); region.click(pattern); return true; } catch (FindFailed e) { return false; } } /** * 查找图片 * * @param legend 图例 * @param minSimilar 最低相似度 * @return 匹配区域 */ public Region findLegend(String legend, double minSimilar) { Pattern pattern = new Pattern(of(legend).getFile().getAbsolutePath()) .similar(minSimilar); Match match = workRegion.findBest(pattern); if (match == null) { return null; } return new Region(match.getRect()); } /** * 等待并查找图例 * * @param legend 图例 * @param minSimilar 最低相似度 * @param timeout 超时时间(秒) * @return 匹配区域 */ public Region waitAndFindLegend(String legend, double minSimilar, long timeout) { Logger.info("等待并查找图例:{}", legend); return TaskUtil.timeTask(() -> { while (JMainService.getInstance().run) { Region result = findLegend(legend, minSimilar); if (result != null) { return result; } } return null; }, timeout, TimeUnit.SECONDS); } /** * 查找并点击图例 * * @param legend 图例 * @param minSimilar 最低相似度 * @return 匹配区域 */ public boolean clickLegend(String legend, double minSimilar) { Pattern pattern = new Pattern(of(legend).getFile().getAbsolutePath()) .similar(minSimilar); Match match = workRegion.findBest(pattern); if (match == null) { return false; } mouseLeftClick(match.getCenter()); return true; } /** * 获取图例 * * @return 图例 */ public Legend of(String legend) { return Legend.inflate(legend); } /** * 将相对坐标转为绝对坐标 * * @return 绝对坐标 */ public Location of(int x, int y) { int ox = getWorkRegion().getX(); int oy = getWorkRegion().getY(); return new Location(x + ox, y + oy); } /** * 将相对坐标转为绝对坐标 * * @param relativePoint 相对坐标 * @return 绝对区域 */ public Location of(Location relativePoint) { return of(relativePoint.getX(), relativePoint.getY()); } /** * 将相对坐标转为色值坐标 * * @return 绝对坐标 */ public ColorLocation of(int x, int y, String hexColor) { int ox = getWorkRegion().getX(); int oy = getWorkRegion().getY(); return new ColorLocation(x + ox, y + oy, hexColor); } /** * 将相对坐标转为色值坐标 * * @return 绝对坐标 */ public String[] of(String... legends) { return legends; } /** * 将相对坐标转为色值坐标 * * @param relativePoint 相对坐标 * @return 绝对区域 */ public ColorLocation of(Location relativePoint, String hexColor) { return of(relativePoint.getX(), relativePoint.getY(), hexColor); } /** * 获取随机中心坐标 * * @param region 区域 * @return 随机中心坐标 */ public Location randomCenter(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); return new Location(x, y); } /** * 输入文本 * * @param text 文本 */ public void input(String text) { // 纯英文输入以type输入,其他情况使用paste输入 if (text.matches("^[a-zA-Z0-9]+$")) { for (char c : text.toCharArray()) { try { // 每个字符输入间隔随机(100-500 毫秒) long delay = RandomUtil.randomLong(100, 500); screen.type(String.valueOf(c)); Thread.sleep(delay); } catch (InterruptedException e) { throw new IllegalArgumentException(e); } } workRegion.type(text); } else { workRegion.paste(text); } } }